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/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/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/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/style.yml b/.github/workflows/style.yml index 98e5fd5150..30c0ff000d 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' @@ -163,3 +169,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/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 efb37d6083..ef654ced1e 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 @@ -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). diff --git a/README_AR.md b/README_AR.md index 4f93802fda..1310a9d802 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) diff --git a/README_BN.md b/README_BN.md index 7599fae9ff..be1f8cc72e 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)। diff --git a/README_CN.md b/README_CN.md index 973629f459..1d4d51e856 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)找到。 diff --git a/README_DE.md b/README_DE.md index 738c0e3b67..7cc2710fde 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). 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..be5d8644e0 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)をご覧ください。 diff --git a/README_KL.md b/README_KL.md index 17e6c9d509..3ebd756929 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). diff --git a/README_KR.md b/README_KR.md index d44723f9b6..ecbe2f6b74 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)에서 확인할 수 있습니다. 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..45aec75dcd 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..94ab6ee338 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. diff --git a/README_TW.md b/README_TW.md index 8263a22b64..c252292e44 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)找到支援的模型提供商完整列表。 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 2cc6410cdd..9dec564c28 100644 --- a/api/.env.example +++ b/api/.env.example @@ -348,6 +348,7 @@ SENTRY_DSN= # DEBUG DEBUG=false +ENABLE_REQUEST_LOGGING=False SQLALCHEMY_ECHO=false # Notion import configuration, support public and internal @@ -476,6 +477,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 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 586f2ded9e..3a258be28f 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -54,7 +54,7 @@ def initialize_extensions(app: DifyApp): ext_otel, ext_proxy_fix, ext_redis, - ext_repositories, + ext_request_logging, ext_sentry, ext_set_secretkey, ext_storage, @@ -75,7 +75,6 @@ def initialize_extensions(app: DifyApp): ext_migrate, ext_redis, ext_storage, - ext_repositories, ext_celery, ext_login, ext_mail, @@ -85,6 +84,7 @@ def initialize_extensions(app: DifyApp): ext_blueprints, ext_commands, 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 3881439ddf..66278a53a3 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 @@ -297,11 +298,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 @@ -444,13 +445,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: @@ -551,11 +552,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: @@ -592,11 +594,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, @@ -668,7 +674,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() @@ -818,8 +824,9 @@ def clear_free_plan_tenant_expired_logs(days: int, batch: int, tenant_ids: list[ 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(): +def clear_orphaned_file_records(force: bool): """ Clear orphaned file records in the database. """ @@ -845,7 +852,15 @@ def clear_orphaned_file_records(): # notify user and ask for confirmation click.echo( - click.style("This command will find and delete orphaned file records in the following tables:", fg="yellow") + 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")) @@ -878,11 +893,55 @@ def clear_orphaned_file_records(): fg="yellow", ) ) - click.confirm("Do you want to proceed?", abort=True) + 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 = [] @@ -964,7 +1023,8 @@ def clear_orphaned_file_records(): 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")) - click.confirm(f"Do you want to proceed to delete all {len(orphaned_files)} orphaned file records?", abort=True) + 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: @@ -979,8 +1039,9 @@ def clear_orphaned_file_records(): 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(): +def remove_orphaned_files_on_storage(force: bool): """ Remove orphaned files on the storage. """ @@ -1028,7 +1089,8 @@ def remove_orphaned_files_on_storage(): fg="yellow", ) ) - click.confirm("Do you want to proceed?", abort=True) + 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")) @@ -1069,7 +1131,8 @@ def remove_orphaned_files_on_storage(): 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")) - click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True) + 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 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 d285515998..1b015b3267 100644 --- a/api/configs/middleware/__init__.py +++ b/api/configs/middleware/__init__.py @@ -1,6 +1,6 @@ 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_settings import BaseSettings @@ -173,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, } 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/vdb/opensearch_config.py b/api/configs/middleware/vdb/opensearch_config.py index 81dde4c04d..96f478e9a6 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,16 @@ 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_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 +48,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/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..de13018f37 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.0", ) COMMIT_SHA: str = Field( 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/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 48353a63af..91058767eb 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 diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 3e908b76a7..f97209c369 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 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 7176440e16..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 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..c475aea9fc 100644 --- a/api/controllers/console/app/workflow_app_log.py +++ b/api/controllers/console/app/workflow_app_log.py @@ -1,6 +1,6 @@ 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 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 5f0762e4a5..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 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..d73d8ce701 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 diff --git a/api/controllers/console/auth/login.py b/api/controllers/console/auth/login.py index 16c1dcc441..27864bab3d 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 diff --git a/api/controllers/console/auth/oauth.py b/api/controllers/console/auth/oauth.py index 89b0df4454..699f693bdb 100644 --- a/api/controllers/console/auth/oauth.py +++ b/api/controllers/console/auth/oauth.py @@ -6,7 +6,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 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 43615af709..981619b0cb 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,14 +526,20 @@ 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() + 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 documents_status.append(marshal(document, document_status_fields)) diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index 3588abeff5..ca18c25e74 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 @@ -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,14 +569,20 @@ 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() + 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 if document.is_paused: @@ -589,14 +601,20 @@ 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() + 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 diff --git a/api/controllers/console/datasets/datasets_segments.py b/api/controllers/console/datasets/datasets_segments.py index 5c54ecbe81..eee09ac32e 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), @@ -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 @@ -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 @@ -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 aee8323f23..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 @@ -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 e4cac40ca1..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 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/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/installed_app.py b/api/controllers/console/explore/installed_app.py index 132da11878..4062972d08 100644 --- a/api/controllers/console/explore/installed_app.py +++ b/api/controllers/console/explore/installed_app.py @@ -2,8 +2,8 @@ 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 @@ -66,7 +66,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 +79,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 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 3a1655d0ee..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 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..49ea81a8a0 100644 --- a/api/controllers/console/explore/wraps.py +++ b/api/controllers/console/explore/wraps.py @@ -1,7 +1,7 @@ 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.wraps import account_initialization_required diff --git a/api/controllers/console/extension.py b/api/controllers/console/extension.py index 833da0d03c..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 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 0d0d7ae95f..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 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 aa1a78935d..eb53dcb16e 100644 --- a/api/controllers/console/workspace/endpoint.py +++ b/api/controllers/console/workspace/endpoint.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 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..a0031307fd 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -1,7 +1,7 @@ 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 @@ -71,7 +71,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 6f9ae18750..fda5a7d3bb 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 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..34af80bca7 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 @@ -88,9 +89,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 +162,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 +226,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 6911181d82..360cbd9246 100644 --- a/api/controllers/console/wraps.py +++ b/api/controllers/console/wraps.py @@ -4,7 +4,7 @@ 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 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..6641632169 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(), diff --git a/api/controllers/inner_api/plugin/plugin.py b/api/controllers/inner_api/plugin/plugin.py index 061ad62a4a..f3a1bd8fa5 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 diff --git a/api/controllers/inner_api/plugin/wraps.py b/api/controllers/inner_api/plugin/wraps.py index c31f9d22ed..709bba3f30 100644 --- a/api/controllers/inner_api/plugin/wraps.py +++ b/api/controllers/inner_api/plugin/wraps.py @@ -3,7 +3,7 @@ from functools import wraps from typing import Optional from flask import request -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from pydantic import BaseModel from sqlalchemy.orm import Session 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 c50f551faf..bd1a23b723 100644 --- a/api/controllers/service_api/app/annotation.py +++ b/api/controllers/service_api/app/annotation.py @@ -1,5 +1,5 @@ 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 diff --git a/api/controllers/service_api/app/app.py b/api/controllers/service_api/app/app.py index 7131e8a310..2c03aba33d 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,7 @@ 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} 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 dfc357e1ab..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 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 ca3e35aab8..e9bb2b046a 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 diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index e1e6f3168f..394f36c3ff 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import marshal, reqparse # type: ignore +from flask_restful import marshal, reqparse from werkzeug.exceptions import Forbidden, NotFound import services.dataset_service @@ -313,7 +313,7 @@ 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: diff --git a/api/controllers/service_api/dataset/document.py b/api/controllers/service_api/dataset/document.py index 9e943e2b2d..44c75f40ef 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 ( @@ -323,7 +323,7 @@ class DocumentDeleteApi(DatasetApiResource): except services.errors.document.DocumentIndexingError: raise DocumentIndexingError("Cannot delete document during indexing.") - return {"result": "success"}, 204 + return 204 class DocumentListApi(DatasetApiResource): @@ -337,7 +337,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 +345,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,14 +374,20 @@ 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() + 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 if document.is_paused: diff --git a/api/controllers/service_api/dataset/metadata.py b/api/controllers/service_api/dataset/metadata.py index 35578eae54..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 diff --git a/api/controllers/service_api/dataset/segment.py b/api/controllers/service_api/dataset/segment.py index 95753cfd67..ea4be4e511 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"}, 204 + return 204 @cloud_edition_billing_resource_check("vector_space", "dataset") def post(self, tenant_id, dataset_id, document_id, segment_id): @@ -344,7 +344,7 @@ class DatasetChildChunkApi(DatasetApiResource): except ChildChunkDeleteIndexServiceError as e: raise ChildChunkDeleteIndexError(str(e)) - return {"result": "success"}, 204 + 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..cd35ceac1d 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 diff --git a/api/controllers/web/app.py b/api/controllers/web/app.py index a84b846112..c9a37af5ed 100644 --- a/api/controllers/web/app.py +++ b/api/controllers/web/app.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.web import api 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/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/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..267dac223d 100644 --- a/api/controllers/web/passport.py +++ b/api/controllers/web/passport.py @@ -1,7 +1,7 @@ import uuid from flask import request -from flask_restful import Resource # type: ignore +from flask_restful import Resource from werkzeug.exceptions import NotFound, Unauthorized from controllers.web import api 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 ab2d4abcd3..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 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..c327c3df18 100644 --- a/api/controllers/web/wraps.py +++ b/api/controllers/web/wraps.py @@ -1,7 +1,7 @@ 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 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..5212d797d8 100644 --- a/api/core/agent/cot_agent_runner.py +++ b/api/core/agent/cot_agent_runner.py @@ -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/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index a1110e7709..611a55b30a 100644 --- a/api/core/agent/fc_agent_runner.py +++ b/api/core/agent/fc_agent_runner.py @@ -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/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index fd0d7fafbd..b74100bb19 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -25,13 +25,11 @@ 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.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.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 services.conversation_service import ConversationService from services.errors.message import MessageNotExistsError @@ -163,12 +161,11 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + 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( @@ -231,12 +228,11 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + 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( @@ -297,12 +293,11 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + 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( 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 1f4db54a9c..735b2a9709 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -9,7 +9,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 ( AdvancedChatAppGenerateEntity, @@ -58,7 +57,7 @@ from core.app.entities.task_entities import ( ) 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.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 @@ -66,11 +65,12 @@ 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.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import 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.enums import CreatorUserRole from models.workflow import ( Workflow, WorkflowRunStatus, @@ -105,15 +105,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, @@ -739,9 +739,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 diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index 995082b79d..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 @@ -223,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 9c3d78a338..d49ff682b9 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -18,16 +18,16 @@ from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.app.apps.workflow.app_queue_manager import WorkflowAppQueueManager from core.app.apps.workflow.app_runner import WorkflowAppRunner from core.app.apps.workflow.generate_response_converter import WorkflowAppGenerateResponseConverter -from core.app.apps.workflow.generate_task_pipeline import WorkflowAppGenerateTaskPipeline from core.app.entities.app_invoke_entities import InvokeFrom, WorkflowAppGenerateEntity 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.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_app_generate_task_pipeline import WorkflowAppGenerateTaskPipeline 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 logger = logging.getLogger(__name__) @@ -138,12 +138,12 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + + 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( @@ -264,12 +264,12 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + + 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( @@ -329,12 +329,12 @@ class WorkflowAppGenerator(BaseAppGenerator): # Create workflow node execution repository session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": application_generate_entity.app_config.tenant_id, - "app_id": application_generate_entity.app_config.app_id, - "session_factory": session_factory, - } + + 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( diff --git a/api/core/app/entities/task_entities.py b/api/core/app/entities/task_entities.py index 817699bd20..0c2d617f80 100644 --- a/api/core/app/entities/task_entities.py +++ b/api/core/app/entities/task_entities.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict from core.model_runtime.entities.llm_entities import LLMResult from core.model_runtime.utils.encoders import jsonable_encoder -from core.workflow.entities.node_entities import AgentNodeStrategyInit +from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunMetadataKey from models.workflow import WorkflowNodeExecutionStatus @@ -244,7 +244,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 @@ -301,13 +301,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[NodeRunMetadataKey, Any]] = None created_at: int finished_at: int files: Optional[Sequence[Mapping[str, Any]]] = [] @@ -370,13 +370,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[NodeRunMetadataKey, Any]] = None created_at: int finished_at: int files: Optional[Sequence[Mapping[str, Any]]] = [] 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..a98a42f5df 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 @@ -9,7 +9,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, @@ -45,6 +44,7 @@ from core.app.entities.task_entities import ( ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline from core.app.task_pipeline.message_cycle_manage import MessageCycleManage +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 ( diff --git a/api/core/app/task_pipeline/message_cycle_manage.py b/api/core/app/task_pipeline/message_cycle_manage.py index fde506639f..a6d826f08b 100644 --- a/api/core/app/task_pipeline/message_cycle_manage.py +++ b/api/core/app/task_pipeline/message_cycle_manage.py @@ -24,7 +24,7 @@ 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 @@ -154,7 +154,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, 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..13c22213c4 100644 --- a/api/core/callback_handler/index_tool_callback_handler.py +++ b/api/core/callback_handler/index_tool_callback_handler.py @@ -1,3 +1,5 @@ +import logging + 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 @@ -7,6 +9,8 @@ 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 +46,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( diff --git a/api/core/entities/provider_configuration.py b/api/core/entities/provider_configuration.py index 86887c9b4a..81d582fb9a 100644 --- a/api/core/entities/provider_configuration.py +++ b/api/core/entities/provider_configuration.py @@ -897,37 +897,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] 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/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/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..e5dbc30689 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 ( @@ -366,7 +368,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 fad7cea01c..34ea3aec26 100644 --- a/api/core/llm_generator/prompts.py +++ b/api/core/llm_generator/prompts.py @@ -1,5 +1,5 @@ # 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. +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 uses could be diverse, which can be English, Chinese, Italian, Español, Arabic, Japanese, French, and etc. ENSURE your output is in the SAME language as the user's input! Your output is restricted only to: (Input language) Intention + Subject(short as possible) @@ -58,7 +58,7 @@ User Input: yo, 你今天咋样? "Your Output": "查询今日我的状态☺️" } -User Input: +User Input: """ # noqa: E501 PYTHON_CODE_GENERATOR_PROMPT_TEMPLATE = ( @@ -163,11 +163,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. +- 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. +- 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 @@ -178,28 +178,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 @@ -214,13 +214,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}} @@ -276,15 +276,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 } 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/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/model_providers/__base/ai_model.py b/api/core/model_runtime/model_providers/__base/ai_model.py index 3c5a2dce4f..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,7 +24,6 @@ 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.impl.model import PluginModelClient @@ -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 6312587861..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 @@ -2,7 +2,7 @@ import logging import time import uuid from collections.abc import Generator, Sequence -from typing import Optional, Union, cast +from typing import Optional, Union from pydantic import ConfigDict @@ -13,14 +13,15 @@ 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.model_runtime.utils.helper import convert_llm_result_chunk_to_str from core.plugin.impl.model import PluginModelClient logger = logging.getLogger(__name__) @@ -238,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, @@ -255,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, @@ -281,9 +292,8 @@ class LargeLanguageModel(AIModel): callbacks=callbacks, ) - text = convert_llm_result_chunk_to_str(chunk.delta.message.content) - current_content = cast(str, assistant_message.content) - assistant_message.content = current_content + text + _update_message_content(chunk.delta.message.content) + real_model = chunk.model if chunk.delta.usage: usage = chunk.delta.usage @@ -293,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( 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/utils/helper.py b/api/core/model_runtime/utils/helper.py index 53789a8e91..5e8a723ec7 100644 --- a/api/core/model_runtime/utils/helper.py +++ b/api/core/model_runtime/utils/helper.py @@ -1,8 +1,6 @@ import pydantic from pydantic import BaseModel -from core.model_runtime.entities.message_entities import PromptMessageContentUnionTypes - def dump_model(model: BaseModel) -> dict: if hasattr(pydantic, "model_dump"): @@ -10,18 +8,3 @@ def dump_model(model: BaseModel) -> dict: return pydantic.model_dump(model) # type: ignore else: return model.model_dump() - - -def convert_llm_result_chunk_to_str(content: None | str | list[PromptMessageContentUnionTypes]) -> str: - 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) - return message_text 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/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index 874b2800b2..f2d1bd305a 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -1,9 +1,9 @@ -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" 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 b229d244f7..120c36f53d 100644 --- a/api/core/ops/langfuse_trace/langfuse_trace.py +++ b/api/core/ops/langfuse_trace/langfuse_trace.py @@ -1,11 +1,10 @@ -import json import logging import os from datetime import datetime, timedelta from typing import Optional from langfuse import Langfuse # type: ignore -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import Session, sessionmaker from core.ops.base_trace_instance import BaseTraceInstance from core.ops.entities.config_entity import LangfuseConfig @@ -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.workflow.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 Account, App, EndUser, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) @@ -113,8 +113,29 @@ 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 + with Session(db.engine, expire_on_commit=False) as session: + # Get the app to find its creator + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + 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}") + + 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 +145,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 +172,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 78a51ff36e..6631727c79 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 @@ -7,7 +6,7 @@ from typing import Optional, cast from langsmith import Client from langsmith.schemas import RunBase -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import Session, sessionmaker from core.ops.base_trace_instance import BaseTraceInstance from core.ops.entities.config_entity import LangSmithConfig @@ -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.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.node_entities import NodeRunMetadataKey +from core.workflow.nodes.enums import NodeType from extensions.ext_database import db -from models.model import EndUser, MessageFile +from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) @@ -137,12 +138,29 @@ 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 + with Session(db.engine, expire_on_commit=False) as session: + # Get the app to find its creator + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + 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}") + + 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 +170,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(NodeRunMetadataKey.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 +199,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 +209,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 a14b5afb8e..c22df55357 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 @@ -7,7 +6,7 @@ from typing import Optional, cast from opik import Opik, Trace from opik.id_helpers import uuid4_to_uuid7 -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import Session, sessionmaker from core.ops.base_trace_instance import BaseTraceInstance from core.ops.entities.config_entity import OpikConfig @@ -22,9 +21,11 @@ from core.ops.entities.trace_entity import ( TraceTaskName, WorkflowTraceInfo, ) -from core.workflow.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.node_entities import NodeRunMetadataKey +from core.workflow.nodes.enums import NodeType from extensions.ext_database import db -from models.model import EndUser, MessageFile +from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) @@ -150,12 +151,29 @@ 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 + with Session(db.engine, expire_on_commit=False) as session: + # Get the app to find its creator + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + 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}") + + 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 +183,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 +211,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 +244,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(NodeRunMetadataKey.TOTAL_TOKENS) or 0 span_data = { "trace_id": opik_trace_id, diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index 2c68055f87..2bcca6ccea 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -16,11 +16,7 @@ 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, - WeaveConfig, ) from core.ops.entities.trace_entity import ( DatasetRetrievalTraceInfo, @@ -33,11 +29,7 @@ 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.opik_trace.opik_trace import OpikDataTrace from core.ops.utils import get_message_data -from core.ops.weave_trace.weave_trace import WeaveDataTrace from extensions.ext_database import db from extensions.ext_storage import storage from models.model import App, AppModelConfig, Conversation, Message, MessageFile, TraceAppConfig @@ -45,36 +37,58 @@ from models.workflow import WorkflowAppLog, WorkflowRun from tasks.ops_trace_task import process_trace_tasks -def build_opik_trace_instance(config: OpikConfig): - return OpikDataTrace(config) - - -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), - }, - TracingProviderEnum.WEAVE.value: { - "config_class": WeaveConfig, - "secret_keys": ["api_key"], - "other_keys": ["project", "entity", "endpoint"], - "trace_instance": WeaveDataTrace, - }, -} +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 { + "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, + } + + 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"], + "trace_instance": WeaveDataTrace, + } + + case _: + raise KeyError(f"Unsupported tracing provider: {provider}") + + +provider_config_map: dict[str, dict[str, Any]] = OpsTraceProviderConfigMap() class OpsTraceManager: diff --git a/api/core/ops/weave_trace/entities/weave_trace_entity.py b/api/core/ops/weave_trace/entities/weave_trace_entity.py index e423f5ccbb..7f489f37ac 100644 --- a/api/core/ops/weave_trace/entities/weave_trace_entity.py +++ b/api/core/ops/weave_trace/entities/weave_trace_entity.py @@ -1,3 +1,4 @@ +from collections.abc import Mapping from typing import Any, Optional, Union from pydantic import BaseModel, Field, field_validator @@ -19,8 +20,8 @@ class WeaveMultiModel(BaseModel): class WeaveTraceModel(WeaveTokenUsage, WeaveMultiModel): id: str = Field(..., description="ID of the trace") op: str = Field(..., description="Name of the operation") - inputs: Optional[Union[str, dict[str, Any], list, None]] = Field(None, description="Inputs of the trace") - outputs: Optional[Union[str, dict[str, Any], list, None]] = Field(None, description="Outputs of the trace") + 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" ) diff --git a/api/core/ops/weave_trace/weave_trace.py b/api/core/ops/weave_trace/weave_trace.py index 49594cb0f1..a4f38dfbba 100644 --- a/api/core/ops/weave_trace/weave_trace.py +++ b/api/core/ops/weave_trace/weave_trace.py @@ -1,4 +1,3 @@ -import json import logging import os import uuid @@ -7,6 +6,7 @@ from typing import Any, Optional, cast import wandb import weave +from sqlalchemy.orm import Session, sessionmaker from core.ops.base_trace_instance import BaseTraceInstance from core.ops.entities.config_entity import WeaveConfig @@ -22,9 +22,11 @@ from core.ops.entities.trace_entity import ( WorkflowTraceInfo, ) from core.ops.weave_trace.entities.weave_trace_entity import WeaveTraceModel +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.node_entities import NodeRunMetadataKey +from core.workflow.nodes.enums import NodeType from extensions.ext_database import db -from models.model import EndUser, MessageFile -from models.workflow import WorkflowNodeExecution +from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) @@ -128,58 +130,57 @@ class WeaveDataTrace(BaseTraceInstance): self.start_call(workflow_run, parent_run_id=trace_info.message_id) - # through workflow_run_id get all_nodes_execution - workflow_nodes_execution_id_records = ( - db.session.query(WorkflowNodeExecution.id) - .filter(WorkflowNodeExecution.workflow_run_id == trace_info.workflow_run_id) - .all() + # through workflow_run_id get all_nodes_execution using repository + session_factory = sessionmaker(bind=db.engine) + # Find the app's creator account + with Session(db.engine, expire_on_commit=False) as session: + # Get the app to find its creator + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + 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}") + + 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, ) - for node_execution_id_record in workflow_nodes_execution_id_records: - node_execution = ( - db.session.query( - WorkflowNodeExecution.id, - WorkflowNodeExecution.tenant_id, - WorkflowNodeExecution.app_id, - WorkflowNodeExecution.title, - WorkflowNodeExecution.node_type, - WorkflowNodeExecution.status, - WorkflowNodeExecution.inputs, - WorkflowNodeExecution.outputs, - WorkflowNodeExecution.created_at, - WorkflowNodeExecution.elapsed_time, - WorkflowNodeExecution.process_data, - WorkflowNodeExecution.execution_metadata, - ) - .filter(WorkflowNodeExecution.id == node_execution_id_record.id) - .first() - ) - - if not node_execution: - continue + # 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 = 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) - attributes = execution_metadata.copy() + execution_metadata = node_execution.metadata if node_execution.metadata else {} + node_total_tokens = execution_metadata.get(NodeRunMetadataKey.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, @@ -192,7 +193,7 @@ class WeaveDataTrace(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": attributes.update( { 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/model.py b/api/core/plugin/backwards_invocation/model.py index 490a475c16..5ec9620f22 100644 --- a/api/core/plugin/backwards_invocation/model.py +++ b/api/core/plugin/backwards_invocation/model.py @@ -239,8 +239,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/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/impl/base.py b/api/core/plugin/impl/base.py index 4f1d808a3e..591e7b0525 100644 --- a/api/core/plugin/impl/base.py +++ b/api/core/plugin/impl/base.py @@ -17,6 +17,7 @@ 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.impl.exc import ( PluginDaemonBadRequestError, @@ -219,6 +220,8 @@ class BasePluginClient: 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/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/rag/datasource/retrieval_service.py b/api/core/rag/datasource/retrieval_service.py index 46a5330bdb..01f74b4a22 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 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/milvus/milvus_vector.py b/api/core/rag/datasource/vdb/milvus/milvus_vector.py index 100bcb198c..7b3f826082 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): @@ -356,11 +357,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..2b47d179d2 100644 --- a/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py +++ b/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py @@ -203,7 +203,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..e23b8d197f 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,12 @@ logger = logging.getLogger(__name__) class OpenSearchConfig(BaseModel): host: str port: int + secure: bool = False + 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 +37,40 @@ 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") 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, + "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 +94,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}}} @@ -234,6 +259,7 @@ class OpenSearchVector(BaseVector): }, } + 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 +278,12 @@ class OpenSearchVectorFactory(AbstractVectorFactory): open_search_config = OpenSearchConfig( host=dify_config.OPENSEARCH_HOST or "localhost", port=dify_config.OPENSEARCH_PORT, + secure=dify_config.OPENSEARCH_SECURE, + 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..0a3738ac93 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)], ) @@ -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), 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/vastbase_vector.py b/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py index a61d571e16..156730ff37 100644 --- a/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py +++ b/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py @@ -58,7 +58,7 @@ 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 ON {table_name} USING hnsw (embedding floatvector_cosine_ops) WITH (m = 16, ef_construction = 64); """ 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/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/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/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..d3605da146 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 ( @@ -149,7 +149,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, @@ -237,12 +237,16 @@ 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, @@ -506,19 +510,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 +664,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 +723,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 +846,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 +896,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: @@ -1005,28 +1036,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/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 index 5c70d50cde..6452317120 100644 --- a/api/core/repositories/__init__.py +++ b/api/core/repositories/__init__.py @@ -4,3 +4,9 @@ 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/repository_registry.py b/api/core/repositories/repository_registry.py deleted file mode 100644 index b66f3ba8e6..0000000000 --- a/api/core/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.repositories.workflow_node_execution import SQLAlchemyWorkflowNodeExecutionRepository -from core.workflow.repository.repository_factory import RepositoryFactory -from extensions.ext_database import db - -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/core/repositories/sqlalchemy_workflow_node_execution_repository.py b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py new file mode 100644 index 0000000000..e73386a936 --- /dev/null +++ b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py @@ -0,0 +1,367 @@ +""" +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.workflow.entities.node_execution_entities import ( + NodeExecution, + NodeExecutionStatus, +) +from core.workflow.nodes.enums import NodeType +from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository +from models import ( + Account, + CreatorUserRole, + EndUser, + WorkflowNodeExecution, + WorkflowNodeExecutionStatus, + 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: NodeExecution + self._node_execution_cache: dict[str, NodeExecution] = {} + + def _to_domain_model(self, db_model: WorkflowNodeExecution) -> NodeExecution: + """ + 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 = db_model.execution_metadata_dict + + # Convert status to domain enum + status = NodeExecutionStatus(db_model.status) + + return NodeExecution( + id=db_model.id, + node_execution_id=db_model.node_execution_id, + workflow_id=db_model.workflow_id, + workflow_run_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: NodeExecution) -> WorkflowNodeExecution: + """ + 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 = WorkflowNodeExecution() + 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_run_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(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: NodeExecution) -> 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[NodeExecution]: + """ + 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}") + return self._node_execution_cache[node_execution_id] + + # 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(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) + + db_model = session.scalar(stmt) + if db_model: + # Convert to domain model + domain_model = self._to_domain_model(db_model) + + # Add to cache + self._node_execution_cache[node_execution_id] = domain_model + + return domain_model + + return None + + def get_by_workflow_run( + self, + workflow_run_id: str, + order_config: Optional[OrderConfig] = None, + ) -> Sequence[NodeExecution]: + """ + 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 + """ + 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) + + db_models = session.scalars(stmt).all() + + # Convert database models to domain models and update cache + domain_models = [] + for model in db_models: + domain_model = self._to_domain_model(model) + # Update cache if node_execution_id is present + if domain_model.node_execution_id: + self._node_execution_cache[domain_model.node_execution_id] = domain_model + domain_models.append(domain_model) + + return domain_models + + def get_running_executions(self, workflow_run_id: str) -> Sequence[NodeExecution]: + """ + 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(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) + + db_models = session.scalars(stmt).all() + domain_models = [] + + for model in db_models: + domain_model = self._to_domain_model(model) + # Update cache if node_execution_id is present + if domain_model.node_execution_id: + self._node_execution_cache[domain_model.node_execution_id] = domain_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(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 "") + ) + + # Clear the in-memory cache + self._node_execution_cache.clear() + logger.info("Cleared in-memory node execution cache") diff --git a/api/core/repositories/workflow_node_execution/__init__.py b/api/core/repositories/workflow_node_execution/__init__.py deleted file mode 100644 index 76e8282b7d..0000000000 --- a/api/core/repositories/workflow_node_execution/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -WorkflowNodeExecution repository implementations. -""" - -from core.repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository - -__all__ = [ - "SQLAlchemyWorkflowNodeExecutionRepository", -] diff --git a/api/core/repositories/workflow_node_execution/sqlalchemy_repository.py b/api/core/repositories/workflow_node_execution/sqlalchemy_repository.py deleted file mode 100644 index b1d37163a4..0000000000 --- a/api/core/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.workflow.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/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/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 3dce1ca293..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 @@ -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/utils/dataset_retriever/dataset_multi_retriever_tool.py b/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py index 032274b87e..04437ea6d8 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 @@ -84,13 +84,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)} @@ -106,12 +110,16 @@ class DatasetMultiRetrieverTool(DatasetRetrieverBaseTool): context_list = [] 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, 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..fff261e0bd 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,12 @@ -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.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 @@ -33,6 +35,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,6 +63,21 @@ 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 = [] external_documents = ExternalDatasetService.fetch_external_knowledge_retrieval( @@ -65,6 +85,7 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): dataset_id=dataset.id, query=query, external_retrieval_parameters=dataset.retrieval_model, + metadata_condition=metadata_condition, ) for external_document in external_documents: document = RetrievalDocument( @@ -100,12 +121,18 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): 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 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 +151,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 = [] @@ -157,12 +185,16 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): 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, 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..257d96133e 100644 --- a/api/core/tools/utils/message_transformer.py +++ b/api/core/tools/utils/message_transformer.py @@ -31,8 +31,8 @@ class ToolFileMessageTransformer: # try to download image try: assert isinstance(message.message, ToolInvokeMessage.TextMessage) - - file = ToolFileManager.create_file_by_url( + tool_file_manager = ToolFileManager() + file = tool_file_manager.create_file_by_url( user_id=user_id, tenant_id=tenant_id, file_url=message.message.text, @@ -60,7 +60,7 @@ 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): @@ -68,7 +68,8 @@ class ToolFileMessageTransformer: # FIXME: should do a type check here. assert isinstance(message.message.blob, bytes) - file = ToolFileManager.create_file_by_raw( + tool_file_manager = ToolFileManager() + file = tool_file_manager.create_file_by_raw( user_id=user_id, tenant_id=tenant_id, conversation_id=conversation_id, 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_execution_entities.py b/api/core/workflow/entities/node_execution_entities.py new file mode 100644 index 0000000000..5e5ead062f --- /dev/null +++ b/api/core/workflow/entities/node_execution_entities.py @@ -0,0 +1,98 @@ +""" +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.entities.node_entities import NodeRunMetadataKey +from core.workflow.nodes.enums import NodeType + + +class NodeExecutionStatus(StrEnum): + """ + Node Execution Status Enum. + """ + + RUNNING = "running" + SUCCEEDED = "succeeded" + FAILED = "failed" + EXCEPTION = "exception" + RETRY = "retry" + + +class NodeExecution(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_run_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: NodeExecutionStatus = NodeExecutionStatus.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[NodeRunMetadataKey, 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[NodeRunMetadataKey, 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/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/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/code/code_node.py b/api/core/workflow/nodes/code/code_node.py index 3c34c5b4e7..804c05f9f4 100644 --- a/api/core/workflow/nodes/code/code_node.py +++ b/api/core/workflow/nodes/code/code_node.py @@ -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: diff --git a/api/core/workflow/nodes/document_extractor/node.py b/api/core/workflow/nodes/document_extractor/node.py index 960d0c3961..8fb1baec89 100644 --- a/api/core/workflow/nodes/document_extractor/node.py +++ b/api/core/workflow/nodes/document_extractor/node.py @@ -11,6 +11,7 @@ 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 @@ -132,6 +133,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 +144,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,6 +170,10 @@ 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}") @@ -214,8 +223,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 +235,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]) @@ -462,3 +471,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/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index 5d466e645f..2c42f5a1be 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -262,7 +262,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 "" diff --git a/api/core/workflow/nodes/http_request/node.py b/api/core/workflow/nodes/http_request/node.py index fd2b0f9ae8..1c82637974 100644 --- a/api/core/workflow/nodes/http_request/node.py +++ b/api/core/workflow/nodes/http_request/node.py @@ -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/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index a7d0aefc6d..a061dfc354 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -353,27 +353,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 = { + NodeRunMetadataKey.ITERATION_ID: self.node_id, + NodeRunMetadataKey.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[NodeRunMetadataKey.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 NodeRunMetadataKey.ITERATION_ID not in current_metadata: + event.route_node_state.node_run_result.metadata = {**current_metadata, **iter_metadata} + return event def _run_single_iter( 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..5955022e5f 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -6,7 +6,7 @@ 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 core.app.app_config.entities import DatasetRetrieveConfigEntity @@ -32,11 +32,11 @@ 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 @@ -264,6 +264,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 +276,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 +294,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 +361,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 +377,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: @@ -493,24 +509,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 @@ -618,7 +634,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/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/node.py b/api/core/workflow/nodes/llm/node.py index 35b146e5d9..eeb44601ec 100644 --- a/api/core/workflow/nodes/llm/node.py +++ b/api/core/workflow/nodes/llm/node.py @@ -1,3 +1,5 @@ +import base64 +import io import json import logging from collections.abc import Generator, Mapping, Sequence @@ -21,7 +23,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,7 +40,6 @@ 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.model_runtime.utils.helper import convert_llm_result_chunk_to_str 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 @@ -95,9 +96,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__) @@ -106,8 +111,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 @@ -215,6 +257,9 @@ class LLMNode(BaseNode[LLMNodeData]): 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, @@ -240,6 +285,7 @@ class LLMNode(BaseNode[LLMNodeData]): ) ) except Exception as e: + logger.exception("error while executing llm node") yield RunCompletedEvent( run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, @@ -268,44 +314,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): - message_text = convert_llm_result_chunk_to_str(invoke_result.message.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 = convert_llm_result_chunk_to_str(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, / @@ -459,7 +506,7 @@ class LLMNode(BaseNode[LLMNodeData]): "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"), + "data_source_type": metadata.get("data_source_type"), "segment_id": metadata.get("segment_id"), "retriever_from": metadata.get("retriever_from"), "score": metadata.get("score"), @@ -750,18 +797,22 @@ class LLMNode(BaseNode[LLMNodeData]): 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] = {} + 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 @@ -963,6 +1014,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. @@ -1123,6 +1210,41 @@ class LLMNode(BaseNode[LLMNodeData]): else SupportStructuredOutputStatus.UNSUPPORTED ) + 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. + + 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. + """ + + # 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( *, contents: Optional[str | list[PromptMessageContentUnionTypes]] = None, role: PromptMessageRole 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_node.py b/api/core/workflow/nodes/loop/loop_node.py index eae33c0a92..bad3e2b928 100644 --- a/api/core/workflow/nodes/loop/loop_node.py +++ b/api/core/workflow/nodes/loop/loop_node.py @@ -337,7 +337,7 @@ class LoopNode(BaseNode[LoopNodeData]): 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, 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/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/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..6a7ad86b51 100644 --- a/api/core/workflow/nodes/variable_assigner/v2/node.py +++ b/api/core/workflow/nodes/variable_assigner/v2/node.py @@ -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/workflow/repository/__init__.py b/api/core/workflow/repository/__init__.py index d91506e72f..672abb6583 100644 --- a/api/core/workflow/repository/__init__.py +++ b/api/core/workflow/repository/__init__.py @@ -6,10 +6,9 @@ for accessing and manipulating data, regardless of the underlying storage mechanism. """ -from core.workflow.repository.repository_factory import RepositoryFactory -from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository __all__ = [ - "RepositoryFactory", + "OrderConfig", "WorkflowNodeExecutionRepository", ] diff --git a/api/core/workflow/repository/repository_factory.py b/api/core/workflow/repository/repository_factory.py deleted file mode 100644 index 45d6f5d842..0000000000 --- a/api/core/workflow/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.workflow.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/workflow/repository/workflow_node_execution_repository.py b/api/core/workflow/repository/workflow_node_execution_repository.py index 9bb790cb0f..3ca9e2ecab 100644 --- a/api/core/workflow/repository/workflow_node_execution_repository.py +++ b/api/core/workflow/repository/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.node_execution_entities import NodeExecution @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 @@ -26,24 +26,28 @@ class WorkflowNodeExecutionRepository(Protocol): application domains or deployment scenarios. """ - def save(self, execution: WorkflowNodeExecution) -> None: + def save(self, execution: NodeExecution) -> 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]: + def get_by_node_execution_id(self, node_execution_id: str) -> Optional[NodeExecution]: """ - 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 """ ... @@ -51,9 +55,9 @@ class WorkflowNodeExecutionRepository(Protocol): self, workflow_run_id: str, order_config: Optional[OrderConfig] = None, - ) -> Sequence[WorkflowNodeExecution]: + ) -> Sequence[NodeExecution]: """ - 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]: + def get_running_executions(self, workflow_run_id: str) -> Sequence[NodeExecution]: """ - 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/app/apps/workflow/generate_task_pipeline.py b/api/core/workflow/workflow_app_generate_task_pipeline.py similarity index 98% rename from api/core/app/apps/workflow/generate_task_pipeline.py rename to api/core/workflow/workflow_app_generate_task_pipeline.py index 67cad9c998..0396fa8157 100644 --- a/api/core/app/apps/workflow/generate_task_pipeline.py +++ b/api/core/workflow/workflow_app_generate_task_pipeline.py @@ -6,7 +6,6 @@ from typing import Optional, Union 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.entities.app_invoke_entities import ( InvokeFrom, @@ -52,13 +51,14 @@ from core.app.entities.task_entities import ( 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.enums import SystemVariableKey from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import 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, @@ -94,15 +94,15 @@ 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, diff --git a/api/core/app/task_pipeline/workflow_cycle_manage.py b/api/core/workflow/workflow_cycle_manager.py similarity index 77% rename from api/core/app/task_pipeline/workflow_cycle_manage.py rename to api/core/workflow/workflow_cycle_manager.py index d720eff73d..6d33d7372c 100644 --- a/api/core/app/task_pipeline/workflow_cycle_manage.py +++ b/api/core/workflow/workflow_cycle_manager.py @@ -46,30 +46,32 @@ from core.app.entities.task_entities import ( ) 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.tools.tool_manager import ToolManager from core.workflow.entities.node_entities import NodeRunMetadataKey +from core.workflow.entities.node_execution_entities import ( + NodeExecution, + NodeExecutionStatus, +) from core.workflow.enums import SystemVariableKey from core.workflow.nodes import NodeType from core.workflow.nodes.tool.entities import ToolNodeData from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.workflow_entry import WorkflowEntry -from models.account import Account -from models.enums import CreatedByRole, WorkflowRunTriggeredFrom -from models.model import EndUser -from models.workflow import ( +from models import ( + Account, + CreatorUserRole, + EndUser, Workflow, - WorkflowNodeExecution, WorkflowNodeExecutionStatus, - WorkflowNodeExecutionTriggeredFrom, WorkflowRun, WorkflowRunStatus, + WorkflowRunTriggeredFrom, ) -class WorkflowCycleManage: +class WorkflowCycleManager: def __init__( self, *, @@ -78,7 +80,6 @@ class WorkflowCycleManage: workflow_node_execution_repository: WorkflowNodeExecutionRepository, ) -> 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 self._workflow_node_execution_repository = workflow_node_execution_repository @@ -89,7 +90,7 @@ class WorkflowCycleManage: session: Session, workflow_id: str, user_id: str, - created_by_role: CreatedByRole, + created_by_role: CreatorUserRole, ) -> WorkflowRun: workflow_stmt = select(Workflow).where(Workflow.id == workflow_id) workflow = session.scalar(workflow_stmt) @@ -258,21 +259,22 @@ class WorkflowCycleManage: 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( + running_domain_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 + # Update the domain models + now = datetime.now(UTC).replace(tzinfo=None) + for domain_execution in running_domain_executions: + if domain_execution.node_execution_id: + # Update the domain model + domain_execution.status = NodeExecutionStatus.FAILED + domain_execution.error = error + domain_execution.finished_at = now + domain_execution.elapsed_time = (now - domain_execution.created_at).total_seconds() - 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() + # Update the repository with the domain model + self._workflow_node_execution_repository.save(domain_execution) if trace_manager: trace_manager.add_trace_task( @@ -286,63 +288,67 @@ class WorkflowCycleManage: 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, - } + def _handle_node_execution_start(self, *, workflow_run: WorkflowRun, event: QueueNodeStartedEvent) -> NodeExecution: + # Create a domain model + created_at = datetime.now(UTC).replace(tzinfo=None) + metadata = { + NodeRunMetadataKey.PARALLEL_MODE_RUN_ID: event.parallel_mode_run_id, + NodeRunMetadataKey.ITERATION_ID: event.in_iteration_id, + NodeRunMetadataKey.LOOP_ID: event.in_loop_id, + } + + domain_execution = NodeExecution( + id=str(uuid4()), + workflow_id=workflow_run.workflow_id, + workflow_run_id=workflow_run.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=NodeExecutionStatus.RUNNING, + metadata=metadata, + created_at=created_at, ) - 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) + # Use the instance repository to save the domain model + self._workflow_node_execution_repository.save(domain_execution) + + return domain_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) -> NodeExecution: + # 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}") - 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) + # 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) - execution_metadata_dict = dict(event.execution_metadata or {}) - execution_metadata = json.dumps(jsonable_encoder(execution_metadata_dict)) if execution_metadata_dict else None + + # 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() - process_data = WorkflowEntry.handle_special_values(event.process_data) + # Update domain model + domain_execution.status = NodeExecutionStatus.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 - 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 + # Update the repository with the domain model + self._workflow_node_execution_repository.save(domain_execution) - # Use the instance repository to update the workflow node execution - self._workflow_node_execution_repository.update(workflow_node_execution) - return workflow_node_execution + return domain_execution def _handle_workflow_node_execution_failed( self, @@ -351,41 +357,52 @@ class WorkflowCycleManage: | QueueNodeInIterationFailedEvent | QueueNodeInLoopFailedEvent | QueueNodeExceptionEvent, - ) -> WorkflowNodeExecution: + ) -> NodeExecution: """ 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) + # 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() - 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 + + # Update domain model + domain_execution.status = ( + NodeExecutionStatus.FAILED if not isinstance(event, QueueNodeExceptionEvent) - else WorkflowNodeExecutionStatus.EXCEPTION.value + else NodeExecutionStatus.EXCEPTION + ) + domain_execution.error = event.error + domain_execution.update_from_mapping( + inputs=inputs, process_data=process_data, outputs=outputs, metadata=execution_metadata_dict ) - 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 + domain_execution.finished_at = finished_at + domain_execution.elapsed_time = elapsed_time - return workflow_node_execution + # 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_run: WorkflowRun, event: QueueNodeRetryEvent - ) -> WorkflowNodeExecution: + ) -> NodeExecution: """ Workflow node execution failed :param workflow_run: workflow run @@ -397,47 +414,47 @@ class WorkflowCycleManage: 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 = { 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 + + # Convert execution metadata keys to strings + execution_metadata_dict: dict[NodeRunMetadataKey, 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 = NodeExecution( + id=str(uuid4()), + workflow_id=workflow_run.workflow_id, + workflow_run_id=workflow_run.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=NodeExecutionStatus.RETRY, + created_at=created_at, + finished_at=finished_at, + elapsed_time=elapsed_time, + error=event.error, + index=event.node_run_index, ) - 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 + + # 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 _workflow_start_to_stream_response( self, @@ -467,7 +484,7 @@ class WorkflowCycleManage: workflow_run: WorkflowRun, ) -> WorkflowFinishStreamResponse: created_by = None - if workflow_run.created_by_role == CreatedByRole.ACCOUNT: + if workflow_run.created_by_role == CreatorUserRole.ACCOUNT: stmt = select(Account).where(Account.id == workflow_run.created_by) account = session.scalar(stmt) if account: @@ -476,7 +493,7 @@ class WorkflowCycleManage: "name": account.name, "email": account.email, } - elif workflow_run.created_by_role == CreatedByRole.END_USER: + 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: @@ -513,9 +530,9 @@ class WorkflowCycleManage: *, event: QueueNodeStartedEvent, task_id: str, - workflow_node_execution: WorkflowNodeExecution, + workflow_node_execution: NodeExecution, ) -> Optional[NodeStartStreamResponse]: - if workflow_node_execution.node_type in {NodeType.ITERATION.value, NodeType.LOOP.value}: + if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: return None if not workflow_node_execution.workflow_run_id: return None @@ -530,7 +547,7 @@ class WorkflowCycleManage: 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, + 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, @@ -563,9 +580,9 @@ class WorkflowCycleManage: | QueueNodeInLoopFailedEvent | QueueNodeExceptionEvent, task_id: str, - workflow_node_execution: WorkflowNodeExecution, + workflow_node_execution: NodeExecution, ) -> Optional[NodeFinishStreamResponse]: - if workflow_node_execution.node_type in {NodeType.ITERATION.value, NodeType.LOOP.value}: + if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: return None if not workflow_node_execution.workflow_run_id: return None @@ -582,16 +599,16 @@ class WorkflowCycleManage: 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, + 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.execution_metadata_dict, + 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_dict or {}), + 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, @@ -606,9 +623,9 @@ class WorkflowCycleManage: *, event: QueueNodeRetryEvent, task_id: str, - workflow_node_execution: WorkflowNodeExecution, + workflow_node_execution: NodeExecution, ) -> Optional[Union[NodeRetryStreamResponse, NodeFinishStreamResponse]]: - if workflow_node_execution.node_type in {NodeType.ITERATION.value, NodeType.LOOP.value}: + if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: return None if not workflow_node_execution.workflow_run_id: return None @@ -625,16 +642,16 @@ class WorkflowCycleManage: 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, + 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.execution_metadata_dict, + 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_dict or {}), + 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, @@ -906,23 +923,6 @@ class WorkflowCycleManage: 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 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_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_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 be47fdc6d6..59982d96ad 100644 --- a/api/extensions/ext_otel.py +++ b/api/extensions/ext_otel.py @@ -6,6 +6,7 @@ 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 @@ -27,6 +28,8 @@ def on_user_loaded(_sender, user): def init_app(app: DifyApp): + from opentelemetry.semconv.trace import SpanAttributes + def is_celery_worker(): return "celery" in sys.argv[0].lower() @@ -37,7 +40,9 @@ def init_app(app: DifyApp): 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}" + "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): @@ -50,7 +55,13 @@ def init_app(app: DifyApp): 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}) + 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) instrumentor = FlaskInstrumentor() if dify_config.DEBUG: @@ -103,8 +114,10 @@ def init_app(app: DifyApp): pass 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.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 @@ -147,19 +160,32 @@ def init_app(app: DifyApp): 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] + 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": - exporter = OTLPSpanExporter( - endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/traces", - headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"}, - ) - metric_exporter = OTLPMetricExporter( - endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/metrics", - headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"}, - ) + 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 = 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() 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 b8cfea121b..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 core.repositories.repository_registry import register_repositories -from dify_app import DifyApp - - -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/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..0b0e2a2f54 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 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 3aa3838def..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 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..afc8f31681 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -13,7 +13,7 @@ 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 configs import dify_config from core.app.features.rate_limiting.rate_limit import RateLimitGenerator 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/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/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..f652449e98 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, @@ -112,7 +112,7 @@ __all__ = [ "CeleryTaskSet", "Conversation", "ConversationVariable", - "CreatedByRole", + "CreatorUserRole", "DataSourceApiKeyAuthBinding", "DataSourceOauthBinding", "Dataset", diff --git a/api/models/account.py b/api/models/account.py index a0b8957fe1..bb6a2a4735 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -1,5 +1,6 @@ import enum import json +from typing import cast from flask_login import UserMixin # type: ignore from sqlalchemy import func @@ -46,13 +47,12 @@ class Account(UserMixin, Base): @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 @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() + ta = db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, account_id=self.id).first() if ta: tenant.current_role = ta.role else: @@ -64,25 +64,23 @@ class Account(UserMixin, Base): 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 + tenant.current_role = join.role self._current_tenant = tenant @property @@ -191,7 +189,7 @@ class TenantAccountRole(enum.StrEnum): } -class Tenant(db.Model): # type: ignore[name-defined] +class Tenant(Base): __tablename__ = "tenants" __table_args__ = (db.PrimaryKeyConstraint("id", name="tenant_pkey"),) @@ -220,7 +218,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 +237,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 +254,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/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..7d9f6068bb 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" diff --git a/api/models/model.py b/api/models/model.py index d1490d75c8..ee79fbd6b5 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -8,6 +8,7 @@ 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 services.plugin.plugin_service import PluginService if TYPE_CHECKING: @@ -15,7 +16,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,15 +24,14 @@ 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 +from .workflow import WorkflowRunStatus if TYPE_CHECKING: from .workflow import Workflow @@ -602,7 +602,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"), @@ -794,7 +794,7 @@ class Conversation(db.Model): # type: ignore[name-defined] for message in messages: if message.workflow_run: - status_counts[message.workflow_run.status] += 1 + status_counts[WorkflowRunStatus(message.workflow_run.status)] += 1 return ( { @@ -864,7 +864,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 +986,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 +1010,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 +1211,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 +1237,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 +1270,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 +1294,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 +1325,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 +1337,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 +1363,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 +1379,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 +1417,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 +1547,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..497cbefc61 100644 --- a/api/models/provider.py +++ b/api/models/provider.py @@ -2,8 +2,7 @@ from enum import Enum from sqlalchemy import func -from models.base import Base - +from .base import Base from .engine import db from .types import StringUUID 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..e027475e38 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 @@ -263,8 +263,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 +304,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 +328,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/workflow.py b/api/models/workflow.py index da60617de5..fd0d279d50 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from models.model import AppMode import sqlalchemy as sa -from sqlalchemy import Index, PrimaryKeyConstraint, func +from sqlalchemy import func from sqlalchemy.orm import Mapped, mapped_column import contexts @@ -18,11 +18,11 @@ from core.helper import encrypter from core.variables import SecretVariable, 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 .enums import CreatorUserRole from .types import StringUUID if TYPE_CHECKING: @@ -429,15 +429,15 @@ class WorkflowRun(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 @property def graph_dict(self): @@ -634,17 +634,17 @@ 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): @@ -736,8 +736,7 @@ 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()")) @@ -756,30 +755,25 @@ 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) + 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_column(db.Text, nullable=False) - created_at = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + created_at = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp(), index=True) updated_at = mapped_column( db.DateTime, nullable=False, server_default=func.current_timestamp(), onupdate=func.current_timestamp() ) diff --git a/api/mypy.ini b/api/mypy.ini index 2898b9b52d..865be3c17d 100644 --- a/api/mypy.ini +++ b/api/mypy.ini @@ -7,3 +7,13 @@ exclude = (?x)( | 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 2a4ad6fea2..18fe5db1a6 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dify-api" -version = "1.3.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~=5.0.0", "flask-login~=0.6.3", "flask-migrate~=4.0.7", "flask-restful~=0.3.10", @@ -63,33 +63,36 @@ dependencies = [ "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.0.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.34", + "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] @@ -116,6 +119,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", @@ -190,7 +194,7 @@ vdb = [ "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/schedule/clean_messages.py b/api/schedule/clean_messages.py index 5e4d3ec323..f41f5264c7 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(): @@ -46,7 +49,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/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 3534471bb1..256935fc6d 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -108,17 +108,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() @@ -297,9 +300,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 @@ -598,7 +601,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: @@ -659,7 +665,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: @@ -688,12 +694,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 @@ -780,7 +786,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.") @@ -793,7 +799,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.") @@ -805,15 +811,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 @@ -835,7 +849,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) @@ -957,7 +971,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 4c63611bb3..503b31ede2 100644 --- a/api/services/agent_service.py +++ b/api/services/agent_service.py @@ -2,7 +2,7 @@ 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 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 a2775fe6ad..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): diff --git a/api/services/app_service.py b/api/services/app_service.py index e87a1c7931..2fae479e05 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 diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 44d2594ee8..bac1bf389f 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -8,8 +8,8 @@ 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 @@ -475,7 +478,9 @@ class DatasetService: 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() + user_permission = ( + db.session.query(DatasetPermission).filter_by(dataset_id=dataset.id, account_id=user.id).first() + ) if ( not user_permission and dataset.tenant_id != user.current_tenant_id @@ -499,23 +504,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 +536,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 +883,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: @@ -980,7 +992,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 +1022,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 +1070,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 +1087,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 +1230,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 +1306,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 +1360,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 +1950,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 +2190,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 +2225,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 +2235,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 +2276,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 +2293,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/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/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..56e06cc33e 100644 --- a/api/services/hit_testing_service.py +++ b/api/services/hit_testing_service.py @@ -69,6 +69,7 @@ class HitTestingService: query: str, account: Account, external_retrieval_model: dict, + metadata_filtering_conditions: dict, ) -> dict: if dataset.provider != "external": return { @@ -82,6 +83,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() 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/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/tag_service.py b/api/services/tag_service.py index 1fbaee96e8..21cb861f87 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 diff --git a/api/services/vector_service.py b/api/services/vector_service.py index 92422bf29d..58292c59f4 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,17 +13,27 @@ 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] = [] + document: Document | None = None for segment in segments: if doc_form == IndexType.PARENT_CHILD_INDEX: - document = DatasetDocument.query.filter_by(id=segment.document_id).first() + document = db.session.query(DatasetDocument).filter_by(id=segment.document_id).first() + if not 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) diff --git a/api/services/website_service.py b/api/services/website_service.py index 460a637a43..3913dc2efe 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 diff --git a/api/services/workflow_app_service.py b/api/services/workflow_app_service.py index e526517b51..a899ebe278 100644 --- a/api/services/workflow_app_service.py +++ b/api/services/workflow_app_service.py @@ -5,7 +5,7 @@ from sqlalchemy import and_, func, or_, select from sqlalchemy.orm import Session from models import App, EndUser, WorkflowAppLog, WorkflowRun -from models.enums import CreatedByRole +from models.enums import CreatorUserRole from models.workflow import WorkflowRunStatus @@ -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 f7c4f500a8..3efdb3a79d 100644 --- a/api/services/workflow_run_service.py +++ b/api/services/workflow_run_service.py @@ -1,16 +1,19 @@ import threading +from collections.abc import Sequence from typing import Optional import contexts -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.workflow.repository.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 ( +from models import ( + Account, + App, + EndUser, WorkflowNodeExecution, WorkflowRun, + WorkflowRunTriggeredFrom, ) @@ -116,7 +119,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[WorkflowNodeExecution]: """ Get workflow run node execution list """ @@ -128,17 +136,18 @@ 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=None, ) # Use the repository to get the node executions with ordering 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) - return list(node_executions) + # Convert domain models to database models + workflow_node_executions = [repository.to_db_model(node_execution) for node_execution in node_executions] + + return workflow_node_executions diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index ebe65e5d5f..06d92b9e29 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -10,9 +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.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.variables import Variable from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.node_execution_entities import NodeExecution, NodeExecutionStatus from core.workflow.errors import WorkflowNodeRunFailedError from core.workflow.graph_engine.entities.event import InNodeEvent from core.workflow.nodes import NodeType @@ -21,12 +22,10 @@ from core.workflow.nodes.enums import ErrorStrategy from core.workflow.nodes.event import RunCompletedEvent from core.workflow.nodes.event.types import NodeEvent from core.workflow.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING -from core.workflow.repository import RepositoryFactory 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 ( @@ -268,37 +267,37 @@ 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 def run_free_workflow_node( self, node_data: dict, tenant_id: str, user_id: str, node_id: str, user_inputs: dict[str, Any] - ) -> WorkflowNodeExecution: + ) -> NodeExecution: """ Run draft workflow node """ @@ -306,7 +305,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 +313,6 @@ class WorkflowService: user_inputs=user_inputs, ), start_at=start_at, - tenant_id=tenant_id, node_id=node_id, ) @@ -322,21 +320,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 - """ + ) -> NodeExecution: try: - node_instance, generator = getter() + node_instance, generator = invoke_node_fn() node_run_result: NodeRunResult | None = None for event in generator: @@ -385,20 +374,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 = NodeExecution( + 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 +397,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 = NodeExecutionStatus.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 = NodeExecutionStatus.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 = NodeExecutionStatus.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: """ 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/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..fd1f6265b4 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.") diff --git a/api/tasks/remove_app_and_related_data_task.py b/api/tasks/remove_app_and_related_data_task.py index dedf1c5334..d9c1980d3f 100644 --- a/api/tasks/remove_app_and_related_data_task.py +++ b/api/tasks/remove_app_and_related_data_task.py @@ -4,16 +4,19 @@ from collections.abc import Callable import click from celery import shared_task # type: ignore -from sqlalchemy import delete +from sqlalchemy import delete, select from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session -from core.workflow.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from extensions.ext_database import db -from models.dataset import AppDatasetJoin -from models.model import ( +from models import ( + Account, ApiToken, + App, AppAnnotationHitHistory, AppAnnotationSetting, + AppDatasetJoin, AppModelConfig, Conversation, EndUser, @@ -188,13 +191,24 @@ def _delete_app_workflow_runs(tenant_id: str, app_id: str): def _delete_app_workflow_node_executions(tenant_id: str, app_id: str): + # Get app's owner + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(Account).where(Account.id == App.owner_id).where(App.id == app_id) + user = session.scalar(stmt) + + if user is None: + errmsg = ( + f"Failed to delete workflow node executions for tenant {tenant_id} and app {app_id}, app's owner not found" + ) + logging.error(errmsg) + raise ValueError(errmsg) + # 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(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, + user=user, + app_id=app_id, + triggered_from=None, ) # Use the clear method to delete all records for this tenant_id and app_id 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/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/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/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/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/workflow/nodes/test_llm.py b/api/tests/integration_tests/workflow/nodes/test_llm.py index 22354df196..777a04bd7f 100644 --- a/api/tests/integration_tests/workflow/nodes/test_llm.py +++ b/api/tests/integration_tests/workflow/nodes/test_llm.py @@ -185,3 +185,38 @@ def test_execute_llm_with_jinja2(setup_code_executor_mock, setup_model_mock): 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", + "data": { + "title": "123", + "type": "llm", + "model": {"provider": "openai", "name": "gpt-3.5-turbo", "mode": "chat", "completion_params": {}}, + "prompt_config": { + "structured_output": { + "enabled": True, + "schema": { + "type": "object", + "properties": {"name": {"type": "string"}, "age": {"type": "number"}}, + }, + } + }, + "prompt_template": [{"role": "user", "text": "{{#sys.query#}}"}], + "memory": None, + "context": {"enabled": False}, + "vision": {"enabled": False}, + }, + }, + ) + 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/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/workflow/graph_engine/test_graph_engine.py b/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py index 2a29ad3e41..f3dbd1836b 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 @@ -864,10 +864,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/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..519dd73787 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 @@ -30,6 +33,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 +53,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 +68,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 @@ -465,3 +492,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/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..94b9d3e2c6 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py @@ -0,0 +1,336 @@ +import json +import time +from datetime import UTC, datetime +from unittest.mock import MagicMock, patch + +import pytest +from sqlalchemy.orm import Session + +from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom +from core.app.entities.queue_entities import ( + QueueNodeFailedEvent, + QueueNodeStartedEvent, + QueueNodeSucceededEvent, +) +from core.workflow.enums import SystemVariableKey +from core.workflow.nodes import NodeType +from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import WorkflowCycleManager +from models.enums import CreatorUserRole +from models.workflow import ( + Workflow, + WorkflowNodeExecutionStatus, + WorkflowRun, + WorkflowRunStatus, +) + + +@pytest.fixture +def mock_app_generate_entity(): + entity = MagicMock(spec=AdvancedChatAppGenerateEntity) + entity.inputs = {"query": "test query"} + entity.invoke_from = InvokeFrom.WEB_APP + # Create app_config as a separate mock + app_config = MagicMock() + app_config.tenant_id = "test-tenant-id" + app_config.app_id = "test-app-id" + entity.app_config = app_config + return entity + + +@pytest.fixture +def mock_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_RUN_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 workflow_cycle_manager(mock_app_generate_entity, mock_workflow_system_variables, mock_node_execution_repository): + return WorkflowCycleManager( + application_generate_entity=mock_app_generate_entity, + workflow_system_variables=mock_workflow_system_variables, + workflow_node_execution_repository=mock_node_execution_repository, + ) + + +@pytest.fixture +def mock_session(): + session = MagicMock(spec=Session) + return session + + +@pytest.fixture +def mock_workflow(): + workflow = MagicMock(spec=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" + workflow.graph = json.dumps({"nodes": [], "edges": []}) + return workflow + + +@pytest.fixture +def mock_workflow_run(): + workflow_run = MagicMock(spec=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.status = WorkflowRunStatus.RUNNING + workflow_run.created_by_role = CreatorUserRole.ACCOUNT + workflow_run.created_by = "test-user-id" + workflow_run.created_at = datetime.now(UTC).replace(tzinfo=None) + workflow_run.inputs_dict = {"query": "test query"} + workflow_run.outputs_dict = {"answer": "test answer"} + return workflow_run + + +def test_init( + workflow_cycle_manager, mock_app_generate_entity, mock_workflow_system_variables, mock_node_execution_repository +): + """Test initialization of WorkflowCycleManager""" + assert workflow_cycle_manager._workflow_run is None + assert workflow_cycle_manager._application_generate_entity == mock_app_generate_entity + assert workflow_cycle_manager._workflow_system_variables == mock_workflow_system_variables + assert workflow_cycle_manager._workflow_node_execution_repository == mock_node_execution_repository + + +def test_handle_workflow_run_start(workflow_cycle_manager, mock_session, mock_workflow): + """Test _handle_workflow_run_start method""" + # Mock session.scalar to return the workflow and max sequence + mock_session.scalar.side_effect = [mock_workflow, 5] + + # Call the method + workflow_run = workflow_cycle_manager._handle_workflow_run_start( + session=mock_session, + workflow_id="test-workflow-id", + user_id="test-user-id", + created_by_role=CreatorUserRole.ACCOUNT, + ) + + # Verify the result + assert workflow_run.tenant_id == mock_workflow.tenant_id + assert workflow_run.app_id == mock_workflow.app_id + assert workflow_run.workflow_id == mock_workflow.id + assert workflow_run.sequence_number == 6 # max_sequence + 1 + assert workflow_run.status == WorkflowRunStatus.RUNNING + assert workflow_run.created_by_role == CreatorUserRole.ACCOUNT + assert workflow_run.created_by == "test-user-id" + + # Verify session.add was called + mock_session.add.assert_called_once_with(workflow_run) + + +def test_handle_workflow_run_success(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _handle_workflow_run_success method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Call the method + result = workflow_cycle_manager._handle_workflow_run_success( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=100, + total_steps=5, + outputs={"answer": "test answer"}, + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.SUCCEEDED + assert result.outputs == json.dumps({"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_session, mock_workflow_run): + """Test _handle_workflow_run_failed method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # 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( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=50, + total_steps=3, + status=WorkflowRunStatus.FAILED, + error="Test error message", + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.FAILED.value + assert result.error == "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_run): + """Test _handle_node_execution_start method""" + # 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_run=mock_workflow_run, + event=event, + ) + + # Verify the result + # NodeExecution doesn't have tenant_id attribute, it's handled at repository level + # assert result.tenant_id == mock_workflow_run.tenant_id + # assert result.app_id == mock_workflow_run.app_id + assert result.workflow_id == mock_workflow_run.workflow_id + assert result.workflow_run_id == mock_workflow_run.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.value + # NodeExecution doesn't have created_by_role and created_by attributes, they're handled at repository level + # assert result.created_by_role == mock_workflow_run.created_by_role + # assert result.created_by == mock_workflow_run.created_by + + # Verify save was called + workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(result) + + +def test_get_workflow_run(workflow_cycle_manager, mock_session, mock_workflow_run): + """Test _get_workflow_run method""" + # Mock session.scalar to return the workflow run + mock_session.scalar.return_value = mock_workflow_run + + # Call the method + result = workflow_cycle_manager._get_workflow_run( + session=mock_session, + workflow_run_id="test-workflow-run-id", + ) + + # Verify the result + assert result == mock_workflow_run + assert workflow_cycle_manager._workflow_run == mock_workflow_run + + +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 = {"metadata": "test metadata"} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + + # Create a mock node execution + node_execution = MagicMock() + node_execution.node_execution_id = "test-node-execution-id" + + # 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.value + + # 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_session, mock_workflow_run): + """Test _handle_workflow_run_partial_success method""" + # Mock _get_workflow_run to return the mock_workflow_run + with patch.object(workflow_cycle_manager, "_get_workflow_run", return_value=mock_workflow_run): + # Call the method + result = workflow_cycle_manager._handle_workflow_run_partial_success( + session=mock_session, + workflow_run_id="test-workflow-run-id", + start_at=time.perf_counter() - 10, # 10 seconds ago + total_tokens=75, + total_steps=4, + outputs={"partial_answer": "test partial answer"}, + exceptions_count=2, + ) + + # Verify the result + assert result == mock_workflow_run + assert result.status == WorkflowRunStatus.PARTIAL_SUCCEEDED.value + assert result.outputs == json.dumps({"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 = {"metadata": "test metadata"} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + event.error = "Test error message" + + # Create a mock node execution + node_execution = MagicMock() + node_execution.node_execution_id = "test-node-execution-id" + + # 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.value + 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/repositories/workflow_node_execution/test_sqlalchemy_repository.py b/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py index c16b453cba..8c10b272fb 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,36 @@ Unit tests for the SQLAlchemy implementation of WorkflowNodeExecutionRepository. """ -from unittest.mock import MagicMock +import json +from datetime import datetime +from unittest.mock import MagicMock, PropertyMock import pytest from pytest_mock import MockerFixture from sqlalchemy.orm import Session, sessionmaker -from core.repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.node_entities import NodeRunMetadataKey +from core.workflow.entities.node_execution_entities import NodeExecution, NodeExecutionStatus +from core.workflow.nodes.enums import NodeType from core.workflow.repository.workflow_node_execution_repository import OrderConfig -from models.workflow import WorkflowNodeExecution +from models.account import Account, Tenant +from models.workflow import WorkflowNodeExecution, WorkflowNodeExecutionStatus, 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 +49,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, ) @@ -45,16 +83,23 @@ def test_save(repository, session): execution = MagicMock(spec=WorkflowNodeExecution) 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): @@ -64,27 +109,47 @@ def test_save_with_existing_tenant_id(repository, session): execution = MagicMock(spec=WorkflowNodeExecution) 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=WorkflowNodeExecution) + 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("core.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=WorkflowNodeExecution) + 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 +157,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("core.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=WorkflowNodeExecution) + 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 +190,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("core.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=WorkflowNodeExecution) + 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 +222,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.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 +259,7 @@ def test_clear(repository, session, mocker: MockerFixture): """Test clear method.""" session_obj, _ = session # Set up mock - mock_delete = mocker.patch("core.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 @@ -176,3 +277,118 @@ def test_clear(repository, session, mocker: MockerFixture): 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 = NodeExecution( + id="test-id", + workflow_id="test-workflow-id", + node_execution_id="test-node-execution-id", + workflow_run_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=NodeExecutionStatus.RUNNING, + error=None, + elapsed_time=1.5, + metadata={NodeRunMetadataKey.TOTAL_TOKENS: 100}, + 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, WorkflowNodeExecution) + 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_run_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 == 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(NodeRunMetadataKey.TOTAL_TOKENS): 100} + + # Create a DB model using our custom subclass + db_model = WorkflowNodeExecution() + 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, NodeExecution) + assert domain_model.id == db_model.id + assert domain_model.workflow_id == db_model.workflow_id + assert domain_model.workflow_run_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 == NodeExecutionStatus(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/uv.lock b/api/uv.lock index d3009e8a66..672755cb8a 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -36,7 +36,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.11.16" +version = "3.11.18" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -47,40 +47,40 @@ 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/63/e7/fa1a8c00e2c54b05dc8cb5d1439f627f7c267874e3f7bb047146116020f9/aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a", size = 7678653 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/10/fd9ee4f9e042818c3c2390054c08ccd34556a3cb209d83285616434cf93e/aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9", size = 712088 }, + { url = "https://files.pythonhosted.org/packages/22/eb/6a77f055ca56f7aae2cd2a5607a3c9e7b9554f1497a069dcfcb52bfc9540/aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b", size = 471450 }, + { url = "https://files.pythonhosted.org/packages/78/dc/5f3c0d27c91abf0bb5d103e9c9b0ff059f60cf6031a5f06f456c90731f42/aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66", size = 457836 }, + { url = "https://files.pythonhosted.org/packages/49/7b/55b65af9ef48b9b811c91ff8b5b9de9650c71147f10523e278d297750bc8/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756", size = 1690978 }, + { url = "https://files.pythonhosted.org/packages/a2/5a/3f8938c4f68ae400152b42742653477fc625d6bfe02e764f3521321c8442/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717", size = 1745307 }, + { url = "https://files.pythonhosted.org/packages/b4/42/89b694a293333ef6f771c62da022163bcf44fb03d4824372d88e3dc12530/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4", size = 1780692 }, + { url = "https://files.pythonhosted.org/packages/e2/ce/1a75384e01dd1bf546898b6062b1b5f7a59b6692ef802e4dd6db64fed264/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f", size = 1676934 }, + { url = "https://files.pythonhosted.org/packages/a5/31/442483276e6c368ab5169797d9873b5875213cbcf7e74b95ad1c5003098a/aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361", size = 1621190 }, + { url = "https://files.pythonhosted.org/packages/7b/83/90274bf12c079457966008a58831a99675265b6a34b505243e004b408934/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1", size = 1658947 }, + { url = "https://files.pythonhosted.org/packages/91/c1/da9cee47a0350b78fdc93670ebe7ad74103011d7778ab4c382ca4883098d/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421", size = 1654443 }, + { url = "https://files.pythonhosted.org/packages/c9/f2/73cbe18dc25d624f79a09448adfc4972f82ed6088759ddcf783cd201956c/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e", size = 1644169 }, + { url = "https://files.pythonhosted.org/packages/5b/32/970b0a196c4dccb1b0cfa5b4dc3b20f63d76f1c608f41001a84b2fd23c3d/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d", size = 1728532 }, + { url = "https://files.pythonhosted.org/packages/0b/50/b1dc810a41918d2ea9574e74125eb053063bc5e14aba2d98966f7d734da0/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f", size = 1750310 }, + { url = "https://files.pythonhosted.org/packages/95/24/39271f5990b35ff32179cc95537e92499d3791ae82af7dcf562be785cd15/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd", size = 1691580 }, + { url = "https://files.pythonhosted.org/packages/6b/78/75d0353feb77f041460564f12fe58e456436bbc00cbbf5d676dbf0038cc2/aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d", size = 417565 }, + { url = "https://files.pythonhosted.org/packages/ed/97/b912dcb654634a813f8518de359364dfc45976f822116e725dc80a688eee/aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6", size = 443652 }, + { url = "https://files.pythonhosted.org/packages/b5/d2/5bc436f42bf4745c55f33e1e6a2d69e77075d3e768e3d1a34f96ee5298aa/aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2", size = 706671 }, + { url = "https://files.pythonhosted.org/packages/fe/d0/2dbabecc4e078c0474abb40536bbde717fb2e39962f41c5fc7a216b18ea7/aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508", size = 466169 }, + { url = "https://files.pythonhosted.org/packages/70/84/19edcf0b22933932faa6e0be0d933a27bd173da02dc125b7354dff4d8da4/aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e", size = 457554 }, + { url = "https://files.pythonhosted.org/packages/32/d0/e8d1f034ae5624a0f21e4fb3feff79342ce631f3a4d26bd3e58b31ef033b/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f", size = 1690154 }, + { url = "https://files.pythonhosted.org/packages/16/de/2f9dbe2ac6f38f8495562077131888e0d2897e3798a0ff3adda766b04a34/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f", size = 1733402 }, + { url = "https://files.pythonhosted.org/packages/e0/04/bd2870e1e9aef990d14b6df2a695f17807baf5c85a4c187a492bda569571/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec", size = 1783958 }, + { url = "https://files.pythonhosted.org/packages/23/06/4203ffa2beb5bedb07f0da0f79b7d9039d1c33f522e0d1a2d5b6218e6f2e/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6", size = 1695288 }, + { url = "https://files.pythonhosted.org/packages/30/b2/e2285dda065d9f29ab4b23d8bcc81eb881db512afb38a3f5247b191be36c/aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009", size = 1618871 }, + { url = "https://files.pythonhosted.org/packages/57/e0/88f2987885d4b646de2036f7296ebea9268fdbf27476da551c1a7c158bc0/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4", size = 1646262 }, + { url = "https://files.pythonhosted.org/packages/e0/19/4d2da508b4c587e7472a032290b2981f7caeca82b4354e19ab3df2f51d56/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9", size = 1677431 }, + { url = "https://files.pythonhosted.org/packages/eb/ae/047473ea50150a41440f3265f53db1738870b5a1e5406ece561ca61a3bf4/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb", size = 1637430 }, + { url = "https://files.pythonhosted.org/packages/11/32/c6d1e3748077ce7ee13745fae33e5cb1dac3e3b8f8787bf738a93c94a7d2/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda", size = 1703342 }, + { url = "https://files.pythonhosted.org/packages/c5/1d/a3b57bfdbe285f0d45572d6d8f534fd58761da3e9cbc3098372565005606/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1", size = 1740600 }, + { url = "https://files.pythonhosted.org/packages/a5/71/f9cd2fed33fa2b7ce4d412fb7876547abb821d5b5520787d159d0748321d/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea", size = 1695131 }, + { url = "https://files.pythonhosted.org/packages/97/97/d1248cd6d02b9de6aa514793d0dcb20099f0ec47ae71a933290116c070c5/aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8", size = 412442 }, + { url = "https://files.pythonhosted.org/packages/33/9a/e34e65506e06427b111e19218a99abf627638a9703f4b8bcc3e3021277ed/aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8", size = 439444 }, ] [[package]] @@ -123,12 +123,21 @@ wheels = [ [[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 } + +[[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 } [[package]] name = "alibabacloud-endpoint-util" @@ -194,7 +203,7 @@ wheels = [ [[package]] name = "alibabacloud-oss-sdk" -version = "0.1.0" +version = "0.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-credentials" }, @@ -203,7 +212,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 } [[package]] name = "alibabacloud-oss-util" @@ -235,7 +244,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/22/8a/ef8ddf5ee0350984c [[package]] name = "alibabacloud-tea-openapi" -version = "0.3.13" +version = "0.3.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-credentials" }, @@ -244,7 +253,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 } [[package]] name = "alibabacloud-tea-util" @@ -300,11 +309,11 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -330,6 +339,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004 }, +] + [[package]] name = "asgiref" version = "3.8.1" @@ -371,16 +392,16 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -654,7 +675,7 @@ wheels = [ [[package]] name = "celery" -version = "5.4.0" +version = "5.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "billiard" }, @@ -664,21 +685,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/bf/03/5d9c6c449248958f1a5870e633a29d7419ff3724c452a98ffd22688a1a6a/celery-5.5.2.tar.gz", hash = "sha256:4d6930f354f9d29295425d7a37261245c74a32807c45d764bedc286afd0e724e", size = 1666892 } 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/04/94/8e825ac1cf59d45d20c4345d4461e6b5263ae475f708d047c3dad0ac6401/celery-5.5.2-py3-none-any.whl", hash = "sha256:54425a067afdc88b57cd8d94ed4af2ffaf13ab8c7680041ac2c4ac44357bdf4c", size = 438626 }, ] [[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 } 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 }, ] [[package]] @@ -726,37 +746,37 @@ wheels = [ [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, ] [[package]] @@ -829,14 +849,14 @@ wheels = [ [[package]] name = "click" -version = "8.1.8" +version = "8.2.0" 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/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857 } 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/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156 }, ] [[package]] @@ -1061,41 +1081,43 @@ sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c [[package]] name = "cryptography" -version = "44.0.2" +version = "44.0.3" 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/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281 }, + { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305 }, + { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040 }, + { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411 }, + { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263 }, + { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198 }, + { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502 }, + { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173 }, + { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713 }, + { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064 }, + { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887 }, + { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737 }, + { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501 }, + { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307 }, + { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876 }, + { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127 }, + { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164 }, + { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081 }, + { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716 }, + { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398 }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900 }, + { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067 }, + { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467 }, + { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375 }, + { url = "https://files.pythonhosted.org/packages/8d/4b/c11ad0b6c061902de5223892d680e89c06c7c4d606305eb8de56c5427ae6/cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375", size = 3390230 }, + { url = "https://files.pythonhosted.org/packages/58/11/0a6bf45d53b9b2290ea3cec30e78b78e6ca29dc101e2e296872a0ffe1335/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647", size = 3895216 }, + { url = "https://files.pythonhosted.org/packages/0a/27/b28cdeb7270e957f0077a2c2bfad1b38f72f1f6d699679f97b816ca33642/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259", size = 4115044 }, + { url = "https://files.pythonhosted.org/packages/35/b0/ec4082d3793f03cb248881fecefc26015813199b88f33e3e990a43f79835/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff", size = 3898034 }, + { url = "https://files.pythonhosted.org/packages/0b/7f/adf62e0b8e8d04d50c9a91282a57628c00c54d4ae75e2b02a223bd1f2613/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5", size = 4114449 }, + { url = "https://files.pythonhosted.org/packages/87/62/d69eb4a8ee231f4bf733a92caf9da13f1c81a44e874b1d4080c25ecbb723/cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c", size = 3134369 }, ] [[package]] @@ -1155,7 +1177,6 @@ wheels = [ [[package]] name = "dify-api" -version = "1.3.0" source = { virtual = "." } dependencies = [ { name = "authlib" }, @@ -1230,11 +1251,10 @@ 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" }, ] @@ -1263,6 +1283,7 @@ dev = [ { name = "types-gevent" }, { name = "types-greenlet" }, { name = "types-html5lib" }, + { name = "types-jsonschema" }, { name = "types-markdown" }, { name = "types-oauthlib" }, { name = "types-objgraph" }, @@ -1334,11 +1355,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 = "~=5.0.0" }, { name = "flask-login", specifier = "~=0.6.3" }, { name = "flask-migrate", specifier = "~=4.0.7" }, { name = "flask-restful", specifier = "~=0.3.10" }, @@ -1385,26 +1406,25 @@ requires-dist = [ { 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.0.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.34" }, + { name = "weave", specifier = "~=0.51.0" }, + { name = "webvtt-py", specifier = "~=0.5.1" }, { name = "yarl", specifier = "~=1.18.3" }, ] @@ -1433,6 +1453,7 @@ dev = [ { name = "types-gevent", specifier = "~=24.11.0" }, { name = "types-greenlet", specifier = "~=3.1.0" }, { name = "types-html5lib", specifier = "~=1.1.11" }, + { 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" }, @@ -1491,8 +1512,8 @@ vdb = [ { 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" }, ] @@ -1709,14 +1730,15 @@ wheels = [ [[package]] name = "flask-cors" -version = "4.0.2" +version = "5.0.1" 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/32/d8/667bd90d1ee41c96e938bafe81052494e70b7abd9498c4a0215c103b9667/flask_cors-5.0.1.tar.gz", hash = "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", size = 11643 } 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/85/61/4aea5fb55be1b6f95e604627dc6c50c47d693e39cab2ac086ee0155a0abd/flask_cors-5.0.1-py3-none-any.whl", hash = "sha256:fa5cb364ead54bbf401a26dbf03030c6b18fb2fcaf70408096a572b409586b0c", size = 11296 }, ] [[package]] @@ -1785,41 +1807,45 @@ wheels = [ [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/ac/8e/6c609cbd0580ae8a0661c408149f196aade7d325b1ae7adc930501b81acb/frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860", size = 115511 }, + { url = "https://files.pythonhosted.org/packages/f2/13/a84804cfde6de12d44ed48ecbf777ba62b12ff09e761f76cdd1ff9e14bb1/frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603", size = 120863 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/39/24/1a1976563fb476ab6f0fa9fefaac7616a4361dbe0461324f9fd7bf425dbe/frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc", size = 115161 }, + { url = "https://files.pythonhosted.org/packages/80/2e/fb4ed62a65f8cd66044706b1013f0010930d8cbb0729a2219561ea075434/frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878", size = 120548 }, + { url = "https://files.pythonhosted.org/packages/71/3e/b04a0adda73bd52b390d730071c0d577073d3d26740ee1bad25c3ad0f37b/frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191", size = 12404 }, ] [[package]] @@ -2017,7 +2043,7 @@ wheels = [ [[package]] name = "google-cloud-bigquery" -version = "3.31.0" +version = "3.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core", extra = ["grpc"] }, @@ -2028,9 +2054,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/9f/cf/174ea37f0410f0702c3582c09bae45d6f43c6eabe2858ab5fb2a4319e15f/google_cloud_bigquery-3.32.0.tar.gz", hash = "sha256:f1c53d73a6d255c8cd0ca7a0c077d95224217427a4b7dcf9913ea0298a2961e8", size = 487055 } 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/5e/c3/f3f6179f54e4b4ac2c6abaa8186054fd1d7d881676bb3caef9688e5fac3d/google_cloud_bigquery-3.32.0-py3-none-any.whl", hash = "sha256:ff38d21d70c4563d2473db288d2a9fe44f071d928bbad6d029ac9ba0b8a36b7a", size = 253121 }, ] [[package]] @@ -2176,28 +2202,28 @@ wheels = [ [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/c5/eb/7551c751a2ea6498907b2fcbe31d7a54b602ba5e8eb9550a9695ca25d25c/greenlet-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:0a16fb934fcabfdfacf21d79e6fed81809d8cd97bc1be9d9c89f0e4567143d7b", size = 295437 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/07/f4/b2a26a309a04fb844c7406a4501331b9400e1dd7dd64d3450472fd47d2e1/greenlet-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b24c7844c0a0afc3ccbeb0b807adeefb7eff2b5599229ecedddcfeb0ef333bec", size = 296239 }, ] [[package]] @@ -2297,11 +2323,11 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -2317,42 +2343,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, ] +[[package]] +name = "hf-xet" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/09/e2fc5ccd6f9828efbd9135d5aab70895fa6891752ce84c57026c48f3f33d/hf_xet-1.1.1.tar.gz", hash = "sha256:3e75d6e04c38c80115b640c025d68c3dc14d62f8b244011dfe547363674a1e87", size = 277864 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/f5/81194ea8e4a585d7d4d0f2ad1ca16e05a4b0c5a385bb2610a8e6da1d2c3d/hf_xet-1.1.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e39a8513f0854656116c837d387d9a41e9d78430b1a181442f04c223cbc4e8f8", size = 5274857 }, + { url = "https://files.pythonhosted.org/packages/55/3c/36342b3fa247f2580821a4b183d38f36fb20e911a8307df625790e734359/hf_xet-1.1.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:c60cd67be384cb9e592fa6dfd29a10fddffa1feb2f3b31f53e980630d1ca0fd6", size = 5079657 }, + { url = "https://files.pythonhosted.org/packages/b0/c1/4f770cc7be79287905e13765d4a7e1949dce3483f90867f532ff56e7126b/hf_xet-1.1.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5efc6cf15930d9b0cef25c0444e00c2f55d9e09f856f26ed8c809fd5cd1aa044", size = 25506200 }, + { url = "https://files.pythonhosted.org/packages/94/69/1ec612f8e9e2ca27563adfca926cfb41bbe988e30d4cd6fc1e0c019e5570/hf_xet-1.1.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:504bbc8341edc2aa4b3c20c1fdda818554ab34e4175730f42e2a90436cbbe706", size = 24469080 }, + { url = "https://files.pythonhosted.org/packages/8d/96/9201773a0ebb982aa5936f19bdd04d404bc5d74e23f30bce6e857757998b/hf_xet-1.1.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87d030157a21016c2cddf757a5fd6f1f364d86afef6e190e63a37dd4dc6f6c98", size = 25641374 }, + { url = "https://files.pythonhosted.org/packages/ba/14/10a4cf526070e774bdc7ce68202dc27a15751ddc22c6b47a5ecb6d8ea4ad/hf_xet-1.1.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6e9b640f0f002b3bea36739b30cf3133b3175c27a342b39315be9a9bdb0cec5b", size = 25425434 }, + { url = "https://files.pythonhosted.org/packages/bd/25/7015a82b3b165747ba85b0383e5d5278d268f3a30460f6d55849903cf272/hf_xet-1.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:215a4e95009a0b9795ca3cf33db4e8d1248139593d7e1185661cd19b062d2b82", size = 4391897 }, +] + [[package]] name = "hiredis" -version = "3.1.0" +version = "3.1.1" 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 }, +sdist = { url = "https://files.pythonhosted.org/packages/49/6f/a1b4749fa7d980f4d11e7f6da42658520fb9a92538844b2012427ad9aa06/hiredis-3.1.1.tar.gz", hash = "sha256:63f22cd7b441cbe13d24087b338e4e6a8f454f333cf35a6ed27ef13a60ca8b0b", size = 87898 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/89/ed8072ca29a52a01a6cf9c8d68141effc35a3a1d511dd6705f15a585fea0/hiredis-3.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:80d98b1d21002c7045ef4c7bae9a41a7a5f6585d08c345850c32ec08d05bd8fe", size = 81254 }, + { url = "https://files.pythonhosted.org/packages/64/90/65251dbbf742c6c8e19735f232d09e1f13fbd033e19d1397e1d46e8ac187/hiredis-3.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:9d943c754273fda5908b6c6f4c64c9dcdc4718bb96fa5c699e7fee687d713566", size = 44605 }, + { url = "https://files.pythonhosted.org/packages/75/bd/28ecc23080e5b74d7bba977829bcd27eec3207809ee6b359594a4d33ab84/hiredis-3.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e0b02238141b685de2e8fcf361d79359a77ca9b440e566280e9dda875de03d1", size = 42535 }, + { url = "https://files.pythonhosted.org/packages/bf/da/bed1270992cd1d1647de9e9cd4ee3902f4c21453de57ab5837e6183ca37f/hiredis-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e2737844ca1c2477808f2612093c9fad68b42dd17fba1b348c95232cf895d84", size = 167838 }, + { url = "https://files.pythonhosted.org/packages/3a/16/28ee85a8a5835259ae427eaf6427a00338651a695074e8a4af657165dc98/hiredis-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf6c2ea105477a7ea837381255f884b60775a8f6b527d16416d0e2fc4dd107d6", size = 178703 }, + { url = "https://files.pythonhosted.org/packages/e5/06/49292341ec3da3ffcd49a23a8fbca4f7a931c37ed00e1521136efcb3dd57/hiredis-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32acf786b0e7117b1d8ffc8e5a1cfab57c73798658ed02228b5e9fa71fd4eaff", size = 168133 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/038d537b8c41caef11a9ee6815e4f1fcf59aab1f222ee48330eaa15d2b85/hiredis-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98aeff9c038fd456e2e1a789abab775a1fcd1fd993170b1602f224e8fb8bc507", size = 168250 }, + { url = "https://files.pythonhosted.org/packages/21/76/3a3da9911a9c98600c72095093629d4646cbcdb4ffc1f2519170a476c801/hiredis-3.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb89a866e61c81ed2da3dc7eaee2b3e70d444aa350aa893321d637e77cda1790", size = 164082 }, + { url = "https://files.pythonhosted.org/packages/ea/66/6043f47f5703152339b11d285ff621eb73d383861289df749d1b84563f0a/hiredis-3.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec1e10e02eaa8df9f43d6e4b3d201cfcc33d08d263f3f1ad59e8433bca4c25e8", size = 162011 }, + { url = "https://files.pythonhosted.org/packages/1d/2d/84ffc08d64b1f5fb09502fe5b8e6557b864c669e330ab65e7f2dded1e741/hiredis-3.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c32869095b412d401ad8358dbb4d8c50661a301237e55fa865c4de83d1a2b5f2", size = 161035 }, + { url = "https://files.pythonhosted.org/packages/06/ff/636262e75da46b1e07ef0e5b27774572245bd20df97d3b409836ea40a3c4/hiredis-3.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ef96546415a0ec22534ee5ce30ca5e0fefc1c1b9f69ded167748fa6b2da55a73", size = 172939 }, + { url = "https://files.pythonhosted.org/packages/83/82/f157db3b867dff34d586948adfc48279ccc8011c2b2b49fb9a685c80da4e/hiredis-3.1.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7bfbc714b3c48f62064e1ff031495c977d7554d9ff3d799bb3f8c40256af94bb", size = 165234 }, + { url = "https://files.pythonhosted.org/packages/6a/5f/3ed30df0b6ac34b48ecbce8a7c633a05720b8d3b446d3ec518a947f8f60b/hiredis-3.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9bd0e4b5a0bd8c5c7137b2fb96eac1a36fca65ab822bfd7e7a712c5f7faf956", size = 163132 }, + { url = "https://files.pythonhosted.org/packages/c1/71/d2e5ed355140178f61a84a102a6b4b30e80da355d86172fa1e14aa11d599/hiredis-3.1.1-cp311-cp311-win32.whl", hash = "sha256:de94a0fbdbf1436a94443be8ddf9357d3f6637a1a072a22442135eca634157cc", size = 20060 }, + { url = "https://files.pythonhosted.org/packages/fe/9a/8ad1c43268fde062e717f5f16e7948c6fc31ae885a90061de79e77d01296/hiredis-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b60488f5ec1d9903b3b0ce744b76c570e82cb1b53d3045df74111a5d5bd2c134", size = 21721 }, + { url = "https://files.pythonhosted.org/packages/22/22/b59206aa280f7cd8b6838393765da19648258c0f7d0a65513ea9ca83d373/hiredis-3.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:b7c3e47b3eec883add6ff6d8dbcc314e7bacd73c5146e4587aa3610a1d59c1b0", size = 81402 }, + { url = "https://files.pythonhosted.org/packages/4a/d2/9c889337dbd812a27725c84773db187f014482708aa21d1f4aac17afe805/hiredis-3.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:dba871b974ebd60258cf723a096a4170cc1241d9a32273513fc9da37410ff4a1", size = 44709 }, + { url = "https://files.pythonhosted.org/packages/de/e3/66e4345a39fbc9efb81191ccba58debc18aae03fbec7048a59624284377b/hiredis-3.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f444c482e817452ccb598140c6544c803480346f572e0b42fece391ed70ff26", size = 42606 }, + { url = "https://files.pythonhosted.org/packages/dc/62/43f9b533bc1020814e0edab0b26ff473b63723a76fb4939c07f5693ba3a5/hiredis-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c63a753a0ba0bb0bc92041682623cab843114a0cf87875cd9aca0ab0d833337", size = 170110 }, + { url = "https://files.pythonhosted.org/packages/dd/6d/6d186f2c0faa486f2615c10f78d85969999118115564fe5efa7ba36c2cbe/hiredis-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93f1981e0f54e74de525266a2dca3f9740ca2eed03227b4f86d1ae8ef887d37b", size = 181043 }, + { url = "https://files.pythonhosted.org/packages/65/b0/75de93fe61a643638bbe8d8197d312a06e20ec64a92a2bcef1746e1deb68/hiredis-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0e371c78b9e4715678ca17a59fc72c37483e53179c9a2d4babf85c383fc55c5", size = 170493 }, + { url = "https://files.pythonhosted.org/packages/d9/bb/582df8cc41f0ab52c364eaf8ba0515a952c757d41d5f3e44d7b1bcc4eda4/hiredis-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d42cd753d4d85cf806037a01e4e6fa83c8db5b20b8d0cbfc2feec3daad2d563f", size = 171187 }, + { url = "https://files.pythonhosted.org/packages/7a/46/46704ab52f3a334d5340d121f0692196059b31af27d1dde9279a79c897ba/hiredis-3.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76b8f64de36c8607650c47951a1591996dcfe186ae158f88bac7e3976348cccc", size = 166707 }, + { url = "https://files.pythonhosted.org/packages/1a/c2/50bbca04b21dd6bf208c429348230a0ef7d64091d3ee4ff2ad54fb204efe/hiredis-3.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1a6f2e54bbad9e0831c5d88451676d7f116210f4f302055a84671ef69c5e935b", size = 164293 }, + { url = "https://files.pythonhosted.org/packages/c1/07/157078368e4262c9029e8254f287ea851f95ec687dc78aed6d957b893af9/hiredis-3.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8619f2a015dd8ba98214e76e7453bcdaaa8b04d81348369ad8975c1ff2792eb3", size = 163146 }, + { url = "https://files.pythonhosted.org/packages/f6/1a/8bc58594b83fd4c94d8d531b061d8fc2af0a4659b5dd7bef5ad294f726d2/hiredis-3.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1fd685aa1f9636da9548fa471abf37138033f1b4ec0d91f515ea5ed4d7d88b62", size = 175236 }, + { url = "https://files.pythonhosted.org/packages/3a/b1/8f7fc62699b11adb453f91fc33bf738a8b73b38274bc392f24b9b2b1e2ff/hiredis-3.1.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:24b51d492b6628054eb4dce63eab0cadf483b87227fe6ee3b6de0038caed6544", size = 167789 }, + { url = "https://files.pythonhosted.org/packages/f5/ab/35715b22dc1962bf6edf0dcde5fe62ca35221c81fe5b946a38e0da4d2f93/hiredis-3.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0c7e43d3968000d75d97b2d24a6f1ee37d24b9a4472ba85f670e7d2d94c6b1f2", size = 165724 }, + { url = "https://files.pythonhosted.org/packages/09/7f/345923dba3b9d6326accd20bddebe5a0a40abe28447581188a5da2c720eb/hiredis-3.1.1-cp312-cp312-win32.whl", hash = "sha256:b48578047c6bb3d0ea3ce37f0762e35e71d1f7cff8d940e2caa131359a12c5a7", size = 20191 }, + { url = "https://files.pythonhosted.org/packages/92/f0/856832bf2558f35ea4db0d594176cf61b10dd6091d9029542362c29631d8/hiredis-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:87b69e99301a33119cb31b19c6be7aed164c0df6b6343ba57b65deb23ae9251e", size = 21796 }, ] [[package]] @@ -2379,15 +2420,15 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -2450,20 +2491,21 @@ socks = [ [[package]] name = "huggingface-hub" -version = "0.30.2" +version = "0.31.1" 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/25/eb/9268c1205d19388659d5dc664f012177b752c0eef194a9159acc7227780f/huggingface_hub-0.31.1.tar.gz", hash = "sha256:492bb5f545337aa9e2f59b75ef4c5f535a371e8958a6ce90af056387e67f1180", size = 403036 } 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/3a/bf/6002da17ec1c7a47bedeb216812929665927c70b6e7500b3c7bf36f01bdd/huggingface_hub-0.31.1-py3-none-any.whl", hash = "sha256:43f73124819b48b42d140cbc0d7a2e6bd15b2853b1b9d728d4d55ad1750cac5b", size = 484265 }, ] [[package]] @@ -2605,29 +2647,20 @@ wheels = [ [[package]] name = "joblib" -version = "1.4.2" +version = "1.5.0" 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/30/08/8bd4a0250247861420a040b33ccf42f43c426ac91d99405374ef117e5872/joblib-1.5.0.tar.gz", hash = "sha256:d8757f955389a3dd7a23152e43bc297c2e0c2d3060056dad0feefc88a06939b5", size = 330234 } 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/da/d3/13ee227a148af1c693654932b8b0b02ed64af5e1f7406d56b088b57574cd/joblib-1.5.0-py3-none-any.whl", hash = "sha256:206144b320246485b712fc8cc51f017de58225fa8b414a1fe1764a7231aca491", size = 307682 }, ] [[package]] name = "json-repair" -version = "0.41.1" +version = "0.44.1" 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/a8/6b/ed6e92efc5acfbc9c35ccae1676b70e4adb1552421e64f838c2a3f097d9a/json_repair-0.44.1.tar.gz", hash = "sha256:1130eb9733b868dac1340b43cb2effebb519ae6d52dd2d0728c6cca517f1e0b4", size = 32886 } 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/82/b4/3cbd27a3240b2962c3b87bbb1c20eb6c56e5b26cde61f141f86ca98e2f68/json_repair-0.44.1-py3-none-any.whl", hash = "sha256:51d82532c3b8263782a301eb7904c75dce5fee8c0d1aba490287fc0ab779ac50", size = 22478 }, ] [[package]] @@ -2647,28 +2680,28 @@ wheels = [ [[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 } 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 }, ] [[package]] name = "kombu" -version = "5.5.2" +version = "5.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "amqp" }, { 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/60/0a/128b65651ed8120460fc5af754241ad595eac74993115ec0de4f2d7bc459/kombu-5.5.3.tar.gz", hash = "sha256:021a0e11fcfcd9b0260ef1fb64088c0e92beb976eb59c1dfca7ddd4ad4562ea2", size = 461784 } 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/5d/35/1407fb0b2f5b07b50cbaf97fce09ad87d3bfefbf64f7171a8651cd8d2f68/kombu-5.5.3-py3-none-any.whl", hash = "sha256:5b0dbceb4edee50aa464f59469d34b97864be09111338cfb224a10b6a163909b", size = 209921 }, ] [[package]] @@ -2825,44 +2858,44 @@ wheels = [ [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243 }, + { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149 }, + { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095 }, ] [[package]] @@ -2915,14 +2948,14 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -3049,15 +3082,6 @@ wheels = [ { 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 }, -] - [[package]] name = "mpmath" version = "1.3.0" @@ -3069,16 +3093,16 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -3111,41 +3135,45 @@ wheels = [ [[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.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/2c/e367dfb4c6538614a0c9453e510d75d66099edf1c4e69da1b5ce691a1931/multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec", size = 89372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e0/53cf7f27eda48fffa53cfd4502329ed29e00efb9e4ce41362cbf8aa54310/multidict-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd", size = 65259 }, + { url = "https://files.pythonhosted.org/packages/44/79/1dcd93ce7070cf01c2ee29f781c42b33c64fce20033808f1cc9ec8413d6e/multidict-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8", size = 38451 }, + { url = "https://files.pythonhosted.org/packages/f4/35/2292cf29ab5f0d0b3613fad1b75692148959d3834d806be1885ceb49a8ff/multidict-6.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad", size = 37706 }, + { url = "https://files.pythonhosted.org/packages/f6/d1/6b157110b2b187b5a608b37714acb15ee89ec773e3800315b0107ea648cd/multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852", size = 226669 }, + { url = "https://files.pythonhosted.org/packages/40/7f/61a476450651f177c5570e04bd55947f693077ba7804fe9717ee9ae8de04/multidict-6.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08", size = 223182 }, + { url = "https://files.pythonhosted.org/packages/51/7b/eaf7502ac4824cdd8edcf5723e2e99f390c879866aec7b0c420267b53749/multidict-6.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229", size = 235025 }, + { url = "https://files.pythonhosted.org/packages/3b/f6/facdbbd73c96b67a93652774edd5778ab1167854fa08ea35ad004b1b70ad/multidict-6.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508", size = 231481 }, + { url = "https://files.pythonhosted.org/packages/70/57/c008e861b3052405eebf921fd56a748322d8c44dcfcab164fffbccbdcdc4/multidict-6.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7", size = 223492 }, + { url = "https://files.pythonhosted.org/packages/30/4d/7d8440d3a12a6ae5d6b202d6e7f2ac6ab026e04e99aaf1b73f18e6bc34bc/multidict-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8", size = 217279 }, + { url = "https://files.pythonhosted.org/packages/7f/e7/bca0df4dd057597b94138d2d8af04eb3c27396a425b1b0a52e082f9be621/multidict-6.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56", size = 228733 }, + { url = "https://files.pythonhosted.org/packages/88/f5/383827c3f1c38d7c92dbad00a8a041760228573b1c542fbf245c37bbca8a/multidict-6.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0", size = 218089 }, + { url = "https://files.pythonhosted.org/packages/36/8a/a5174e8a7d8b94b4c8f9c1e2cf5d07451f41368ffe94d05fc957215b8e72/multidict-6.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777", size = 225257 }, + { url = "https://files.pythonhosted.org/packages/8c/76/1d4b7218f0fd00b8e5c90b88df2e45f8af127f652f4e41add947fa54c1c4/multidict-6.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2", size = 234728 }, + { url = "https://files.pythonhosted.org/packages/64/44/18372a4f6273fc7ca25630d7bf9ae288cde64f29593a078bff450c7170b6/multidict-6.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618", size = 230087 }, + { url = "https://files.pythonhosted.org/packages/0f/ae/28728c314a698d8a6d9491fcacc897077348ec28dd85884d09e64df8a855/multidict-6.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7", size = 223137 }, + { url = "https://files.pythonhosted.org/packages/22/50/785bb2b3fe16051bc91c70a06a919f26312da45c34db97fc87441d61e343/multidict-6.4.3-cp311-cp311-win32.whl", hash = "sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378", size = 34959 }, + { url = "https://files.pythonhosted.org/packages/2f/63/2a22e099ae2f4d92897618c00c73a09a08a2a9aa14b12736965bf8d59fd3/multidict-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589", size = 38541 }, + { url = "https://files.pythonhosted.org/packages/fc/bb/3abdaf8fe40e9226ce8a2ba5ecf332461f7beec478a455d6587159f1bf92/multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676", size = 64019 }, + { url = "https://files.pythonhosted.org/packages/7e/b5/1b2e8de8217d2e89db156625aa0fe4a6faad98972bfe07a7b8c10ef5dd6b/multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1", size = 37925 }, + { url = "https://files.pythonhosted.org/packages/b4/e2/3ca91c112644a395c8eae017144c907d173ea910c913ff8b62549dcf0bbf/multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a", size = 37008 }, + { url = "https://files.pythonhosted.org/packages/60/23/79bc78146c7ac8d1ac766b2770ca2e07c2816058b8a3d5da6caed8148637/multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054", size = 224374 }, + { url = "https://files.pythonhosted.org/packages/86/35/77950ed9ebd09136003a85c1926ba42001ca5be14feb49710e4334ee199b/multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc", size = 230869 }, + { url = "https://files.pythonhosted.org/packages/49/97/2a33c6e7d90bc116c636c14b2abab93d6521c0c052d24bfcc231cbf7f0e7/multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07", size = 231949 }, + { url = "https://files.pythonhosted.org/packages/56/ce/e9b5d9fcf854f61d6686ada7ff64893a7a5523b2a07da6f1265eaaea5151/multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde", size = 231032 }, + { url = "https://files.pythonhosted.org/packages/f0/ac/7ced59dcdfeddd03e601edb05adff0c66d81ed4a5160c443e44f2379eef0/multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c", size = 223517 }, + { url = "https://files.pythonhosted.org/packages/db/e6/325ed9055ae4e085315193a1b58bdb4d7fc38ffcc1f4975cfca97d015e17/multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae", size = 216291 }, + { url = "https://files.pythonhosted.org/packages/fa/84/eeee6d477dd9dcb7691c3bb9d08df56017f5dd15c730bcc9383dcf201cf4/multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3", size = 228982 }, + { url = "https://files.pythonhosted.org/packages/82/94/4d1f3e74e7acf8b0c85db350e012dcc61701cd6668bc2440bb1ecb423c90/multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507", size = 226823 }, + { url = "https://files.pythonhosted.org/packages/09/f0/1e54b95bda7cd01080e5732f9abb7b76ab5cc795b66605877caeb2197476/multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427", size = 222714 }, + { url = "https://files.pythonhosted.org/packages/e7/a2/f6cbca875195bd65a3e53b37ab46486f3cc125bdeab20eefe5042afa31fb/multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731", size = 233739 }, + { url = "https://files.pythonhosted.org/packages/79/68/9891f4d2b8569554723ddd6154375295f789dc65809826c6fb96a06314fd/multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713", size = 230809 }, + { url = "https://files.pythonhosted.org/packages/e6/72/a7be29ba1e87e4fc5ceb44dabc7940b8005fd2436a332a23547709315f70/multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a", size = 226934 }, + { url = "https://files.pythonhosted.org/packages/12/c1/259386a9ad6840ff7afc686da96808b503d152ac4feb3a96c651dc4f5abf/multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124", size = 35242 }, + { url = "https://files.pythonhosted.org/packages/06/24/c8fdff4f924d37225dc0c56a28b1dca10728fc2233065fafeb27b4b125be/multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db", size = 38635 }, + { url = "https://files.pythonhosted.org/packages/96/10/7d526c8974f017f1e7ca584c71ee62a638e9334d8d33f27d7cdfc9ae79e4/multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9", size = 10400 }, ] [[package]] @@ -3175,11 +3203,11 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -3323,7 +3351,7 @@ wheels = [ [[package]] name = "onnxruntime" -version = "1.21.0" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coloredlogs" }, @@ -3334,14 +3362,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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/89/a5/1c6c10322201566015183b52ef011dfa932f5dd1b278de8d75c3b948411d/onnxruntime-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:03d3ef7fb11adf154149d6e767e21057e0e577b947dd3f66190b212528e1db31", size = 12691517 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/36/b4/3f1c71ce1d3d21078a6a74c5483bfa2b07e41a8d2b8fb1e9993e6a26d8d3/onnxruntime-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0d534a43d1264d1273c2d4f00a5a588fa98d21117a3345b7104fa0bbcaadb9a", size = 12692233 }, ] [[package]] @@ -3365,15 +3393,15 @@ wheels = [ [[package]] name = "opendal" -version = "0.45.17" +version = "0.45.18" 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/9f/62/09ae9ab9d194e27eee24c6df87f0a5359ce73a82862d8bc429a69be8fb41/opendal-0.45.18.tar.gz", hash = "sha256:1434fff5e9ead95847596ee22ae183c6f137205d294e88d6154eae1a515fdb46", size = 982265 } 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/30/8e/f23a4ed495e1c7ec47e7c23c0d58cee5726ad584d1d9212b64f39455b332/opendal-0.45.18-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9e07cfa433e69779befcca4f528b5085cc095af3308691ad4f5ebe37c28ec346", size = 26928339 }, + { url = "https://files.pythonhosted.org/packages/12/e3/7e3d2d09de3ab89f6e100d32b172ae4864dca988996e212afeec62f88214/opendal-0.45.18-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb6d42972f9941e76b600626ed069d2c9f2bc143116a72475504550e6d387dd2", size = 12937936 }, + { url = "https://files.pythonhosted.org/packages/84/51/1b324dad0da585c7ba430449683ab6f4faa83841153d5c331b1672216229/opendal-0.45.18-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42166f049c3deae4cd406afb026ddca9e41ea1da8c7c4cd0cb76d4bdee35c87a", size = 14477843 }, + { url = "https://files.pythonhosted.org/packages/12/db/5f4d5998a5e93cbba07a99c09864e873373bc71683eda52ee4750acfcdac/opendal-0.45.18-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ae0e4ce1cf7b3c66f0b11022387dece6cc7ed7d0821a0b30f90efb41cad20d26", size = 13464183 }, + { url = "https://files.pythonhosted.org/packages/be/bb/857ebd60932c6e175a0ef4fb21a98292d9483131e411b09b3ef8bc837c18/opendal-0.45.18-cp311-abi3-win_amd64.whl", hash = "sha256:6d8d2cb268efd41a8ad20f716324e4b7f7f340e8e16f52166715dc0bef3061dc", size = 15159073 }, ] [[package]] @@ -3664,7 +3692,7 @@ wheels = [ [[package]] name = "opik" -version = "1.3.5" +version = "1.3.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -3676,13 +3704,14 @@ 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/d8/16/b37208d6a77f3cc74750cff4e0970e6f596aef0f491a675a40aa879157e6/opik-1.3.6.tar.gz", hash = "sha256:25d6fa8b7aa1ef23d10d598040e539440912c12b765eabfc75c8780bbbfc8ad3", size = 177174 } 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/d4/3f/e9d14a97f85d34505770b7c7715bd72bbfc40a778163818f0d3e871264bb/opik-1.3.6-py3-none-any.whl", hash = "sha256:888973c2a1276d68c9b3cf26d8078db8aa675d2c907edda328cdab4995a8e29b", size = 341630 }, ] [[package]] @@ -3708,37 +3737,40 @@ wheels = [ [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/a9/a3/6ea878e7b4a0dc5c888d0370d7752dcb23f402747d10e2257478d69b5e63/orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1", size = 142695 }, + { url = "https://files.pythonhosted.org/packages/79/2a/4048700a3233d562f0e90d5572a849baa18ae4e5ce4c3ba6247e4ece57b0/orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a", size = 134603 }, + { url = "https://files.pythonhosted.org/packages/03/45/10d934535a4993d27e1c84f1810e79ccf8b1b7418cef12151a22fe9bb1e1/orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5", size = 131400 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683 }, + { url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754 }, + { url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218 }, ] [[package]] @@ -3874,41 +3906,48 @@ wheels = [ [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809 }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338 }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309 }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768 }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751 }, ] [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, ] [[package]] @@ -3969,31 +4008,30 @@ wheels = [ [[package]] name = "posthog" -version = "3.23.0" +version = "4.0.1" 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/cf/6f/835a48728adb60b51dbe6bcc528480e38f1f80769a48704f687d83aefe7e/posthog-4.0.1.tar.gz", hash = "sha256:77e7ebfc6086972db421d3e05c91d5431b2b964865d33a9a32e55dd88da4bff8", size = 78040 } 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/cd/f0/8141c04bf105e7fe71b2803fe2193d74a127b447fd149b3e93711ca450c5/posthog-4.0.1-py2.py3-none-any.whl", hash = "sha256:0c76cbab3e5ab0096c4f591c0b536465478357270f926d11ff833c97984659d8", size = 92029 }, ] [[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 } 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 }, ] [[package]] @@ -4051,16 +4089,16 @@ wheels = [ [[package]] name = "protobuf" -version = "4.25.6" +version = "4.25.7" 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/74/63/84fdeac1f03864c2b8b9f0b7fe711c4af5f95759ee281d2026530086b2f5/protobuf-4.25.7.tar.gz", hash = "sha256:28f65ae8c14523cc2c76c1e91680958700d3eac69f45c96512c12c63d9a38807", size = 380612 } 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/41/ed/9a58076cfb8edc237c92617f1d3744660e9b4457d54f3c2fdf1a4bbae5c7/protobuf-4.25.7-cp310-abi3-win32.whl", hash = "sha256:dc582cf1a73a6b40aa8e7704389b8d8352da616bc8ed5c6cc614bdd0b5ce3f7a", size = 392457 }, + { url = "https://files.pythonhosted.org/packages/28/b3/e00870528029fe252cf3bd6fa535821c276db3753b44a4691aee0d52ff9e/protobuf-4.25.7-cp310-abi3-win_amd64.whl", hash = "sha256:cd873dbddb28460d1706ff4da2e7fac175f62f2a0bebc7b33141f7523c5a2399", size = 413446 }, + { url = "https://files.pythonhosted.org/packages/60/1d/f450a193f875a20099d4492d2c1cb23091d65d512956fb1e167ee61b4bf0/protobuf-4.25.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4c899f09b0502eb39174c717ccf005b844ea93e31137c167ddcacf3e09e49610", size = 394248 }, + { url = "https://files.pythonhosted.org/packages/c8/b8/ea88e9857484a0618c74121618b9e620fc50042de43cdabbebe1b93a83e0/protobuf-4.25.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:6d2f5dede3d112e573f0e5f9778c0c19d9f9e209727abecae1d39db789f522c6", size = 293717 }, + { url = "https://files.pythonhosted.org/packages/a7/81/d0b68e9a9a76804113b6dedc6fffed868b97048bbe6f1bedc675bdb8523c/protobuf-4.25.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:d41fb7ae72a25fcb79b2d71e4247f0547a02e8185ed51587c22827a87e5736ed", size = 294636 }, + { url = "https://files.pythonhosted.org/packages/17/d7/1e7c80cb2ea2880cfe38580dcfbb22b78b746640c9c13fc3337a6967dc4c/protobuf-4.25.7-py3-none-any.whl", hash = "sha256:e9d969f5154eaeab41404def5dcf04e62162178f4b9de98b2d3c1c70f5f84810", size = 156468 }, ] [[package]] @@ -4184,76 +4222,92 @@ wheels = [ [[package]] name = "pydantic" -version = "2.9.2" +version = "2.11.4" 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/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540 } 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/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900 }, ] [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, ] [[package]] name = "pydantic-extra-types" -version = "2.9.0" +version = "2.10.4" 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/d9/33/0cde418479949cd6aa1ac669deffcd1c37d8d9cead99ddb48f344e75f2e3/pydantic_extra_types-2.10.4.tar.gz", hash = "sha256:bf8236a63d061eb3ecb1b2afa78ba0f97e3f67aa11dbbff56ec90491e8772edc", size = 95269 } 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/df/ac/bee195ee49256385fad460ce420aeb42703a648dba487c20b6fd107e42ea/pydantic_extra_types-2.10.4-py3-none-any.whl", hash = "sha256:ce064595af3cab05e39ae062752432dcd0362ff80f7e695b61a3493a4d842db7", size = 37276 }, ] [[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 } 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 }, ] [[package]] @@ -4281,7 +4335,7 @@ crypto = [ [[package]] name = "pymilvus" -version = "2.5.6" +version = "2.5.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio" }, @@ -4292,9 +4346,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/3b/64/3bc30ab75104a21b9622915b93ffe42f6d250d5d16113624407fcfd42a12/pymilvus-2.5.8.tar.gz", hash = "sha256:48923e7efeebcc366d32b644772796f60484e0ca1a5afc1606d21a10ed98133c", size = 1260355 } 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/6f/96/2ce2a0b601d95e373897eb2334f83dba615bd5647b0e4908ff30959920d2/pymilvus-2.5.8-py3-none-any.whl", hash = "sha256:6f33c9e78c041373df6a94724c90ca83448fd231aa33d6298a7a84ed2a5a0236", size = 227647 }, ] [[package]] @@ -4322,17 +4376,19 @@ wheels = [ [[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 } +sdist = { url = "https://files.pythonhosted.org/packages/13/f7/fcc99e41bac6aee0822667809957800b2f512f2d96d1fa52c283cb58db16/pyobvector-0.1.20.tar.gz", hash = "sha256:26e8155d5b933333a2ab21b08e51df84e374d188f5fb26520347450e72f34c6e", size = 35256 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/29/f9ca51e5ca104cccbcb3d58f897338eb0110dd2e7e35fecb7e4af2b49504/pyobvector-0.1.18-py3-none-any.whl", hash = "sha256:9ca4098fd58f87e9c6ff1cd4a5631c666d51d0607933dd3656b7274eacc36428", size = 36210 }, + { url = "https://files.pythonhosted.org/packages/e6/7d/00cd33c18814bec0a4f6f4a89becdbe2c6c28d78f87d765d0259a22b117e/pyobvector-0.1.20-py3-none-any.whl", hash = "sha256:1a991f8b9f53e1a749fc396ccb90c7591e64b2c5679930023f00789c62e7af72", size = 46264 }, ] [[package]] @@ -4367,31 +4423,31 @@ wheels = [ [[package]] name = "pypdf" -version = "5.4.0" +version = "5.5.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/e0/c8/543f8ae1cd9e182e9f979d9ab1df18e3445350471abadbdabc0166ae5741/pypdf-5.5.0.tar.gz", hash = "sha256:8ce6a18389f7394fd09a1d4b7a34b097b11c19088a23cfd09e5008f85893e254", size = 5021690 } 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/a1/4e/931b90b51e3ebc69699be926b3d5bfdabae2d9c84337fd0c9fb98adbf70c/pypdf-5.5.0-py3-none-any.whl", hash = "sha256:2f61f2d32dde00471cd70b8977f98960c64e84dd5ba0d070e953fcb4da0b2a73", size = 303371 }, ] [[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 } 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/90/47/eda4904f715fb98561e34012826e883816945934a851745570521ec89520/pypdfium2-4.30.0-py3-none-win32.whl", hash = "sha256:ee2410f15d576d976c2ab2558c93d392a25fb9f6635e8dd0a8a3a5241b275e0e", size = 2775951 }, + { url = "https://files.pythonhosted.org/packages/25/bd/56d9ec6b9f0fc4e0d95288759f3179f0fcd34b1a1526b75673d2f6d5196f/pypdfium2-4.30.0-py3-none-win_amd64.whl", hash = "sha256:90dbb2ac07be53219f56be09961eb95cf2473f834d01a42d901d13ccfad64b4c", size = 2892098 }, + { url = "https://files.pythonhosted.org/packages/be/7a/097801205b991bc3115e8af1edb850d30aeaf0118520b016354cf5ccd3f6/pypdfium2-4.30.0-py3-none-win_arm64.whl", hash = "sha256:119b2969a6d6b1e8d55e99caaf05290294f2d0fe49c12a3f17102d01c441bd29", size = 2752118 }, ] [[package]] @@ -4722,7 +4778,7 @@ wheels = [ [[package]] name = "readabilipy" -version = "0.2.0" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -4730,14 +4786,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 } 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 }, ] [[package]] name = "realtime" -version = "2.4.2" +version = "2.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -4745,21 +4801,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/75/fc/ef69bd4a1bf30a5435bc2d09f6c33bfef5f317746b1a4ca2932ef14b22fc/realtime-2.4.3.tar.gz", hash = "sha256:152febabc822ce60e11f202842c5aa6858ae4bd04920bfd6a00c1dd492f426b0", size = 18849 } 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/29/0c/68ce3db6354c466f68bba2be0fe0ad3a93dca8219e10b9bad3138077efec/realtime-2.4.3-py3-none-any.whl", hash = "sha256:09ff3b61ac928413a27765640b67362380eaddba84a7037a17972a64b1ac52f7", size = 22086 }, ] [[package]] name = "redis" -version = "5.0.8" +version = "6.0.0" 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/79/12/dffaaa4374b8d5f3b7ff5c40025c9db387e06264302d5a9da6043cd84e1f/redis-6.0.0.tar.gz", hash = "sha256:5446780d2425b787ed89c91ddbfa1be6d32370a636c8fdb687f11b1c26c1fa88", size = 4620969 } 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/08/c8/68081c9d3531f7b2a4d663326b96a9dcbc2aef47df3c6b5c38dea90dff02/redis-6.0.0-py3-none-any.whl", hash = "sha256:a2e040aee2cdd947be1fa3a32e35a956cd839cc4c1dbbe4b2cdee5b9623fd27c", size = 268950 }, ] [package.optional-dependencies] @@ -4821,7 +4877,7 @@ wheels = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -4829,9 +4885,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 } 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 }, ] [[package]] @@ -4861,14 +4917,15 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -4944,39 +5001,39 @@ wheels = [ [[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 } 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 }, ] [[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.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453 }, + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566 }, + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020 }, + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935 }, + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971 }, + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631 }, + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236 }, + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436 }, + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759 }, + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985 }, + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775 }, + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307 }, + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026 }, + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627 }, + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340 }, + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080 }, ] [[package]] @@ -5015,15 +5072,15 @@ wheels = [ [[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 } 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 }, ] [package.optional-dependencies] @@ -5035,43 +5092,43 @@ flask = [ [[package]] name = "setproctitle" -version = "1.3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/4d/6a840c8d2baa07b57329490e7094f90aac177a1d5226bc919046f1106860/setproctitle-1.3.5.tar.gz", hash = "sha256:1e6eaeaf8a734d428a95d8c104643b39af7d247d604f40a7bebcf3960a853c5e", size = 26737 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/4a/9e0243c5df221102fb834a947f5753d9da06ad5f84e36b0e2e93f7865edb/setproctitle-1.3.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c8dcc250872385f2780a5ea58050b58cbc8b6a7e8444952a5a65c359886c593", size = 17256 }, - { url = "https://files.pythonhosted.org/packages/c7/a1/76ad2ba6f5bd00609238e3d64eeded4598e742a5f25b5cc1a0efdae5f674/setproctitle-1.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ca82fae9eb4800231dd20229f06e8919787135a5581da245b8b05e864f34cc8b", size = 11893 }, - { url = "https://files.pythonhosted.org/packages/47/3a/75d11fedff5b21ba9a4c5fe3dfa5e596f831d094ef1896713a72e9e38833/setproctitle-1.3.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0424e1d33232322541cb36fb279ea5242203cd6f20de7b4fb2a11973d8e8c2ce", size = 31631 }, - { url = "https://files.pythonhosted.org/packages/5a/12/58220de5600e0ed2e5562297173187d863db49babb03491ffe9c101299bc/setproctitle-1.3.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fec8340ab543144d04a9d805d80a0aad73fdeb54bea6ff94e70d39a676ea4ec0", size = 32975 }, - { url = "https://files.pythonhosted.org/packages/fa/c4/fbb308680d83c1c7aa626950308318c6e6381a8273779163a31741f3c752/setproctitle-1.3.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eab441c89f181271ab749077dcc94045a423e51f2fb0b120a1463ef9820a08d0", size = 30126 }, - { url = "https://files.pythonhosted.org/packages/31/6e/baaf70bd9a881dd8c12cbccdd7ca0ff291024a37044a8245e942e12e7135/setproctitle-1.3.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c371550a2288901a0dcd84192691ebd3197a43c95f3e0b396ed6d1cedf5c6c", size = 31135 }, - { url = "https://files.pythonhosted.org/packages/a6/dc/d8ab6b1c3d844dc14f596e3cce76604570848f8a67ba6a3812775ed2c015/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78288ff5f9c415c56595b2257ad218936dd9fa726b36341b373b31ca958590fe", size = 30874 }, - { url = "https://files.pythonhosted.org/packages/d4/84/62a359b3aa51228bd88f78b44ebb0256a5b96dd2487881c1e984a59b617d/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f1f13a25fc46731acab518602bb1149bfd8b5fabedf8290a7c0926d61414769d", size = 29893 }, - { url = "https://files.pythonhosted.org/packages/e2/d6/b3c52c03ee41e7f006e1a737e0db1c58d1dc28e258b83548e653d0c34f1c/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1534d6cd3854d035e40bf4c091984cbdd4d555d7579676d406c53c8f187c006f", size = 32293 }, - { url = "https://files.pythonhosted.org/packages/55/09/c0ba311879d9c05860503a7e2708ace85913b9a816786402a92c664fe930/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62a01c76708daac78b9688ffb95268c57cb57fa90b543043cda01358912fe2db", size = 30247 }, - { url = "https://files.pythonhosted.org/packages/9e/43/cc7155461f0b5a48aebdb87d78239ff3a51ebda0905de478d9fa6ab92d9c/setproctitle-1.3.5-cp311-cp311-win32.whl", hash = "sha256:ea07f29735d839eaed985990a0ec42c8aecefe8050da89fec35533d146a7826d", size = 11476 }, - { url = "https://files.pythonhosted.org/packages/e7/57/6e937ac7aa52db69225f02db2cfdcb66ba1db6fdc65a4ddbdf78e214f72a/setproctitle-1.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:ab3ae11e10d13d514d4a5a15b4f619341142ba3e18da48c40e8614c5a1b5e3c3", size = 12189 }, - { url = "https://files.pythonhosted.org/packages/2b/19/04755958495de57e4891de50f03e77b3fe9ca6716a86de00faa00ad0ee5a/setproctitle-1.3.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:523424b9be4dea97d95b8a584b183f35c7bab2d0a3d995b01febf5b8a8de90e4", size = 17250 }, - { url = "https://files.pythonhosted.org/packages/b9/3d/2ca9df5aa49b975296411dcbbe272cdb1c5e514c43b8be7d61751bb71a46/setproctitle-1.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b6ec1d86c1b4d7b5f2bdceadf213310cf24696b82480a2a702194b8a0bfbcb47", size = 11878 }, - { url = "https://files.pythonhosted.org/packages/36/d6/e90e23b4627e016a4f862d4f892be92c9765dd6bf1e27a48e52cd166d4a3/setproctitle-1.3.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea6c505264275a43e9b2acd2acfc11ac33caf52bc3167c9fced4418a810f6b1c", size = 31940 }, - { url = "https://files.pythonhosted.org/packages/15/13/167cdd55e00a8e10b36aad79646c3bf3c23fba0c08a9b8db9b74622c1b13/setproctitle-1.3.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b91e68e6685998e6353f296100ecabc313a6cb3e413d66a03d74b988b61f5ff", size = 33370 }, - { url = "https://files.pythonhosted.org/packages/9b/22/574a110527df133409a75053b7d6ff740993ccf30b8713d042f26840d351/setproctitle-1.3.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc1fda208ae3a2285ad27aeab44c41daf2328abe58fa3270157a739866779199", size = 30628 }, - { url = "https://files.pythonhosted.org/packages/52/79/78b05c7d792c9167b917acdab1773b1ff73b016560f45d8155be2baa1a82/setproctitle-1.3.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:828727d220e46f048b82289018300a64547b46aaed96bf8810c05fe105426b41", size = 31672 }, - { url = "https://files.pythonhosted.org/packages/b0/62/4509735be062129694751ac55d5e1fbb6d86fa46a8689b7d5e2c23dae5b0/setproctitle-1.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:83b016221cf80028b2947be20630faa14e3e72a403e35f0ba29550b4e856767b", size = 31378 }, - { url = "https://files.pythonhosted.org/packages/72/e7/b394c55934b89f00c2ef7d5e6f18cca5d8dfa26ef628700c4de0c85e3f3d/setproctitle-1.3.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6d8a411e752e794d052434139ca4234ffeceeb8d8d8ddc390a9051d7942b2726", size = 30370 }, - { url = "https://files.pythonhosted.org/packages/13/ee/e1f27bf52d2bec7060bb6311ab0ccede8de98ed5394e3a59e7a14a453fb5/setproctitle-1.3.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50cfbf86b9c63a2c2903f1231f0a58edeb775e651ae1af84eec8430b0571f29b", size = 32875 }, - { url = "https://files.pythonhosted.org/packages/6e/08/13b561085d2de53b9becfa5578545d99114e9ff2aa3dc151bcaadf80b17e/setproctitle-1.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f3b5e2eacd572444770026c9dd3ddc7543ce427cdf452d40a408d1e95beefb30", size = 30903 }, - { url = "https://files.pythonhosted.org/packages/65/f0/6cd06fffff2553be7b0571447d0c0ef8b727ef44cc2d6a33452677a311c8/setproctitle-1.3.5-cp312-cp312-win32.whl", hash = "sha256:cf4e3ded98027de2596c6cc5bbd3302adfb3ca315c848f56516bb0b7e88de1e9", size = 11468 }, - { url = "https://files.pythonhosted.org/packages/c1/8c/e8a7cb568c4552618838941b332203bfc77ab0f2d67c1cb8f24dee0370ec/setproctitle-1.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:f7a8c01ffd013dda2bed6e7d5cb59fbb609e72f805abf3ee98360f38f7758d9b", size = 12190 }, +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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488 }, + { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487 }, + { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208 }, ] [[package]] name = "setuptools" -version = "78.1.0" +version = "80.4.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/95/32/0cc40fe41fd2adb80a2f388987f4f8db3c866c69e33e0b4c8b093fdf700e/setuptools-80.4.0.tar.gz", hash = "sha256:5a78f61820bc088c8e4add52932ae6b8cf423da2aff268c23f813cfbb13b4006", size = 1315008 } 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/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812 }, ] [[package]] @@ -5148,11 +5205,11 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -5184,6 +5241,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/c6/33c706449cdd92b1b6d756b247761e27d32230fd6b2de5f44c4c3e5632b2/SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1", size = 1881276 }, ] +[[package]] +name = "sqlglot" +version = "26.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/86/461568bd63a43fd3c9d2dec42f9070905cd7b1a0a7e27cacfe91c2e386ff/sqlglot-26.17.1.tar.gz", hash = "sha256:518c649ff4ae9601e2f156758c21d3552db8a109872f1228e0f6e89d3712bf73", size = 5356122 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/30/2b50dd8bc227cce28aa7cb4dfa8fc174d34d1b7fb65128055d308ab6af7a/sqlglot-26.17.1-py3-none-any.whl", hash = "sha256:8772f5c1d095a9600cfbe5bdd08bc345d84f875773735a6cbe6cd5abcfa43900", size = 460536 }, +] + [[package]] name = "starlette" version = "0.41.0" @@ -5242,14 +5308,14 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -5336,61 +5402,51 @@ wheels = [ [[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 } 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/5b/64/b16003419a1d7728d0d8c0d56a4c24325e7b10a21a9dd1fc0f7115c02f0a/tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16", size = 894897 }, ] [[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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506 }, + { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481 }, ] [[package]] @@ -5458,7 +5514,7 @@ wheels = [ [[package]] name = "transformers" -version = "4.35.2" +version = "4.51.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -5472,14 +5528,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 } 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 }, ] [[package]] name = "typer" -version = "0.15.2" +version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -5487,9 +5543,9 @@ 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/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641 } 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/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253 }, ] [[package]] @@ -5560,14 +5616,14 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -5614,6 +5670,18 @@ 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 }, ] +[[package]] +name = "types-jsonschema" +version = "4.23.0.20241208" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/e6/9e5cd771687086844caa43dbb211ec0d1cfa899d17c110f3220efcd46e83/types_jsonschema-4.23.0.20241208.tar.gz", hash = "sha256:e8b15ad01f290ecf6aea53f93fbdf7d4730e4600313e89e8a7f95622f7e87b7c", size = 14770 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/64/4b2fba8b7cb0104ba013f2a1bf6f39a98e927e14befe1ef947d373b25218/types_jsonschema-4.23.0.20241208-py3-none-any.whl", hash = "sha256:87934bd9231c99d8eff94cacfc06ba668f7973577a9bd9e1f9de957c5737313e", size = 15021 }, +] + [[package]] name = "types-markdown" version = "3.7.0.20250322" @@ -5652,11 +5720,11 @@ wheels = [ [[package]] name = "types-openpyxl" -version = "3.1.5.20250306" +version = "3.1.5.20250506" 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/97/1a/063e0c279d359b68cc8444e5281057a2ffacf661e48398712c64fef75eb6/types_openpyxl-3.1.5.20250506.tar.gz", hash = "sha256:c82d748895dbedd59ef36ba6b0079c588c4d1d302311b406752845c83aa4c707", size = 100483 } 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/cc/84/c813f40a325327f7673a3b6c8c98c4ea71c4cb84a129523e507ed6fa9e1d/types_openpyxl-3.1.5.20250506-py3-none-any.whl", hash = "sha256:7fe8cb309d11edc3d9f1a778823f49d77422c59352180fc066607854b22e9683", size = 166054 }, ] [[package]] @@ -5736,11 +5804,11 @@ wheels = [ [[package]] name = "types-pywin32" -version = "310.0.0.20250319" +version = "310.0.0.20250429" 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/a2/b9/1876634fc40be589bb6fa1abf2741e4a9cbad6e29848dea1f73aab7c7182/types_pywin32-310.0.0.20250429.tar.gz", hash = "sha256:3efd94825c53fe9d996356215891141b9736584e835c83eb2bbf2fa573e9668c", size = 328388 } 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/e7/17/987cac084174b9bd3e2d17c7a7bc2d2dff77303143ebe550a6b9d792a664/types_pywin32-310.0.0.20250429-py3-none-any.whl", hash = "sha256:1c7944ec77abe665d249678d24e141321ff8b8f755eca8b6c1e5c4b20e617e76", size = 390427 }, ] [[package]] @@ -5818,28 +5886,28 @@ wheels = [ [[package]] name = "types-tensorflow" -version = "2.18.0.20250404" +version = "2.18.0.20250506" 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/cd/b4/f5ec306fe6f678e7f1e0beec6aa5065dfa0a3442c0791c2d1d9f6ed10010/types_tensorflow-2.18.0.20250506.tar.gz", hash = "sha256:bf366668592982af33ddec16efa07dfcc57cab9129b98f1acb8c3ad4908bc4e6", size = 257733 } 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/0f/bd/11dcefa1d7fc157dab409b2f511ff9cb2d17715d6297f8be22abe19d64ef/types_tensorflow-2.18.0.20250506-py3-none-any.whl", hash = "sha256:b4f326f692e5d20f90e0a9899361b6327aa2c88af0d6a9c49490d8136e5ccec2", size = 329285 }, ] [[package]] name = "types-tqdm" -version = "4.67.0.20250404" +version = "4.67.0.20250513" 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/d4/74/a77b5179e3543853c51ce786b300cd253934477c81aab4d786dff9894724/types_tqdm-4.67.0.20250513.tar.gz", hash = "sha256:907028c8d0a8fc20072132cd0cee72a3b6c72abf32f5ff914a7749e7d13b351e", size = 17207 } 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/6a/7b/996a534691afd516f60fa3ad3f4101b38f7222fff6c1b12f508a4c817695/types_tqdm-4.67.0.20250513-py3-none-any.whl", hash = "sha256:73d2bdac28bab49235d8660aece6c415636a0fb406f7a24b39737dfc6bf6a5dd", size = 24060 }, ] [[package]] @@ -5853,11 +5921,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.13.1" +version = "4.13.2" 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/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } 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/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, ] [[package]] @@ -5873,6 +5941,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, ] +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -5882,6 +5962,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026 }, +] + [[package]] name = "ujson" version = "5.9.0" @@ -5961,24 +6053,22 @@ pptx = [ [[package]] name = "unstructured-client" -version = "0.28.1" +version = "0.34.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" }, + { name = "typing-inspection" }, ] -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/ac/32/9e819deaa5a59b57d97055b6c2cb9a83494e2f9c0fb07f56b3030bd1490f/unstructured_client-0.34.0.tar.gz", hash = "sha256:bc1c34edc622545993f1061127996da2576fc602fefd23e5cd8454e04c421e1f", size = 81006 } 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/a9/e3/d1c2d02d953555d2830af3013d5ce76351507441f148f81469ae751bec7c/unstructured_client-0.34.0-py3-none-any.whl", hash = "sha256:3180d2030695fe6279e7f6f3a1fb92b4038f26c5706e6f9dfe063f816893b734", size = 189417 }, ] [[package]] @@ -6004,11 +6094,11 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -6042,15 +6132,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.34.0" +version = "0.34.2" 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/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815 } 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/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483 }, ] [package.optional-dependencies] @@ -6086,11 +6176,11 @@ wheels = [ [[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 } 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 }, ] [[package]] @@ -6122,7 +6212,7 @@ wheels = [ [[package]] name = "wandb" -version = "0.18.3" +version = "0.19.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -6131,23 +6221,26 @@ dependencies = [ { 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/cc/57/8a61979c40a7a0a5206ef3369ed474326135bf292f172019f35dca97a235/wandb-0.18.3.tar.gz", hash = "sha256:eb2574cea72bc908c6ce1b37edf7a889619e6e06e1b4714eecfe0662ded43c06", size = 8686381 } +sdist = { url = "https://files.pythonhosted.org/packages/39/98/0ff2925a21b998d4b84731429f4554ca3d9b5cad42c09c075e7306c3aca0/wandb-0.19.11.tar.gz", hash = "sha256:3f50a27dfadbb25946a513ffe856c0e8e538b5626ef207aa50b00c3b0356bff8", size = 39511477 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/4a/6fa1d584ecd69cea5b9943ec5cfa36276cbd567efa8709135a7e4ab89cfb/wandb-0.18.3-py3-none-any.whl", hash = "sha256:7da64f7da0ff7572439de10bfd45534e8811e71e78ac2ccc3b818f1c0f3a9aef", size = 5015658 }, - { url = "https://files.pythonhosted.org/packages/59/8f/deef595ca67833ea5aceb5da5fc10759a5e8f8bce85b17761b1614fa2ba9/wandb-0.18.3-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:6674d8a5c40c79065b9c7eb765136756d5ebc9457a5f9abc820a660fb23f8b67", size = 10081571 }, - { url = "https://files.pythonhosted.org/packages/06/85/b55642d095407369dd7ad1d8ea1e7f410d60fcdb6c29bcc9afb1e5522d51/wandb-0.18.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:741f566e409a2684d3047e4cc25e8e914d78196b901190937b24b6abb8b052e5", size = 10008319 }, - { url = "https://files.pythonhosted.org/packages/b4/53/5387afaab29876e669973b3bb5bda829e3c10e509caef59f614bf20c0106/wandb-0.18.3-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:8be5e877570b693001c52dcc2089e48e6a4dcbf15f3adf5c9349f95148b59d58", size = 10250633 }, - { url = "https://files.pythonhosted.org/packages/bd/79/2fa554283afa7259e296313160164947daf52e0d42b04d6ecf9c5af01e15/wandb-0.18.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d788852bd4739fa18de3918f309c3a955b5cef3247fae1c40df3a63af637e1a0", size = 12339454 }, - { url = "https://files.pythonhosted.org/packages/86/a6/11eaa16c96469b4d6fc0fb3271e70d5bbe2c3a93c15fc677de9a1aa4374a/wandb-0.18.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab81424eb207d78239a8d69c90521a70074fb81e3709055484e43c76fe44dc08", size = 12970950 }, - { url = "https://files.pythonhosted.org/packages/13/dd/ccaa5a51e2557368300eec9e362b5688151e45a052e33017633baa3011a9/wandb-0.18.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2c91315b8b62423eae18577d66a4b4bb8e4341a7d5c849cb2963e3b3dff0bf6d", size = 13038220 }, - { url = "https://files.pythonhosted.org/packages/bc/6f/fabbf2161078556384ef48f3db89182773010cdd14900986004e702b85f5/wandb-0.18.3-py3-none-win32.whl", hash = "sha256:92a647dab783938ec87776a9fae8a13e72e6dad939c53e357cdea9d2570f0ad8", size = 12573298 }, - { url = "https://files.pythonhosted.org/packages/d8/7b/e94b46d620d26b2e1f486f2746febdcb6579be20f361355b40263ddd8262/wandb-0.18.3-py3-none-win_amd64.whl", hash = "sha256:29cac2cfa3124241fed22cfedc9a52e1500275ee9bbb0b428ce4bf63c4723bf0", size = 12573303 }, + { url = "https://files.pythonhosted.org/packages/4f/2c/f8bab58c73fdde4442f1baffd9ea5d1bb3113906a97a27e8d9ab72db7a69/wandb-0.19.11-py3-none-any.whl", hash = "sha256:ff3bf050ba25ebae7aedc9a775ffab90c28068832edfe5458423f488c2558f82", size = 6481327 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/44/74/dbe9277dd935b77dd16939cdf15357766fec0813a6e336cf5f1d07eb016e/wandb-0.19.11-py3-none-win32.whl", hash = "sha256:38dea43c7926d8800405a73b80b9adfe81eb315fc6f2ac6885c77eb966634421", size = 20767584 }, + { url = "https://files.pythonhosted.org/packages/36/d5/215cac3edec5c5ac6e7231beb9d22466d5d4e4a132fa3a1d044f7d682c15/wandb-0.19.11-py3-none-win_amd64.whl", hash = "sha256:73402003c56ddc2198878492ab2bff55bb49bce5587eae5960e737d27c0c48f7", size = 20767588 }, ] [[package]] @@ -6198,13 +6291,14 @@ wheels = [ [[package]] name = "weave" -version = "0.51.43" +version = "0.51.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "diskcache" }, { name = "emoji" }, { name = "gql", extra = ["aiohttp", "requests"] }, { name = "jsonschema" }, + { name = "nest-asyncio" }, { name = "numpy" }, { name = "packaging" }, { name = "pydantic" }, @@ -6213,24 +6307,23 @@ dependencies = [ { name = "uuid-utils" }, { name = "wandb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/b4/8fb1e21bc0b0442be9c4c5e4644847596cd75a35a313a5887f1eadda8da2/weave-0.51.43.tar.gz", hash = "sha256:bab4ba6f7ba33f1975e5f6399b7fc4ad6b25c0e2cd22d197bb9358a7b9596b91", size = 368936 } +sdist = { url = "https://files.pythonhosted.org/packages/6b/08/4f7cf06bd01eb2f95cebfcf402972ce1b16f13051f712aee96c54de0631f/weave-0.51.46.tar.gz", hash = "sha256:014b25c1aa1a3d402aebad6bd173c2eab0be9ab526903494734e4566bf064dde", size = 394578 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/40/1e374d3f1f8389a4228426b5a87aae7428a7eb74dfa633de98d86796eb41/weave-0.51.43-py3-none-any.whl", hash = "sha256:2e9faa0e21bd5a6fea363142891ee4f2e347951b98f0d7082acb0273432cb940", size = 473685 }, + { url = "https://files.pythonhosted.org/packages/70/2e/c15f7b2ac26a0242d700f62e2f7ab247569e5c18c9375a0d54d3f13f9019/weave-0.51.46-py3-none-any.whl", hash = "sha256:a2168e5b241af1b46309309c993af498ec87b6021b566271c2d8ac7b89b9bb6a", size = 503946 }, ] [[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 } 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 }, ] [[package]] @@ -6282,6 +6375,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/ed/aad7e0f5a462d679f7b4d2e0d8502c3096740c883b5bbed5103146480937/webvtt_py-0.5.1-py3-none-any.whl", hash = "sha256:9d517d286cfe7fc7825e9d4e2079647ce32f5678eb58e39ef544ffbb932610b7", size = 19802 }, +] + [[package]] name = "werkzeug" version = "3.1.3" @@ -6350,11 +6452,11 @@ wheels = [ [[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 } 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 }, ] [[package]] diff --git a/dev/mypy-check b/dev/mypy-check index 23e3776b1a..c043faffe6 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 --cache-fine-grained --sqlite-cache . 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 83d975cec5..7871f71a98 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`. @@ -472,7 +482,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= @@ -520,9 +530,13 @@ RELYT_DATABASE=postgres # open search configuration, only available when VECTOR_STORE is `opensearch` OPENSEARCH_HOST=opensearch OPENSEARCH_PORT=9200 +OPENSEARCH_SECURE=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 @@ -855,7 +869,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 @@ -1071,6 +1085,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 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 9ab1304492..65fbd007e2 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.0 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.0 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.0 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -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.9-local + image: langgenius/dify-plugin-daemon:0.0.10-local restart: always environment: # Use the shared environment variables. @@ -188,7 +188,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.middleware.yaml b/docker/docker-compose.middleware.yaml index 01c7573a95..4f39e801f5 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.9-local + image: langgenius/dify-plugin-daemon:0.0.10-local restart: always env_file: - ./middleware.env @@ -123,7 +123,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 8edcd497c6..0aca3b749e 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} @@ -225,9 +226,12 @@ 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_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} @@ -475,6 +479,7 @@ x-shared-env: &shared-api-worker-env 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} @@ -488,7 +493,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.0 restart: always environment: # Use the shared environment variables. @@ -517,7 +522,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.0 restart: always environment: # Use the shared environment variables. @@ -543,7 +548,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.3.0 + image: langgenius/dify-web:1.4.0 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -604,7 +609,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 @@ -628,7 +633,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.0.9-local + image: langgenius/dify-plugin-daemon:0.0.10-local restart: always environment: # Use the shared environment variables. @@ -674,7 +679,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/middleware.env.example b/docker/middleware.env.example index 1a4484a9b5..2ac09ea264 100644 --- a/docker/middleware.env.example +++ b/docker/middleware.env.example @@ -144,4 +144,4 @@ 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= 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..6d7bb9e54b 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/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/.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/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)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 078c7ebd8c..5619b1e445 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -98,7 +98,7 @@ const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { 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/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' : 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application' } target='_blank' rel='noopener noreferrer' diff --git a/web/app/(commonLayout)/datasets/Container.tsx b/web/app/(commonLayout)/datasets/Container.tsx index 771fa7c3f9..b484c0445d 100644 --- a/web/app/(commonLayout)/datasets/Container.tsx +++ b/web/app/(commonLayout)/datasets/Container.tsx @@ -85,7 +85,7 @@ const Container = () => { return (
-
+
setActiveTab(newActiveTab)} diff --git a/web/app/(commonLayout)/datasets/Doc.tsx b/web/app/(commonLayout)/datasets/Doc.tsx index 20264ce8ad..efdfe157f2 100644 --- a/web/app/(commonLayout)/datasets/Doc.tsx +++ b/web/app/(commonLayout)/datasets/Doc.tsx @@ -121,7 +121,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => { )}
-
+
{Template}
diff --git a/web/app/(commonLayout)/datasets/template/template.en.mdx b/web/app/(commonLayout)/datasets/template/template.en.mdx index 54e08b45d8..2fe9de59dc 100644 --- a/web/app/(commonLayout)/datasets/template/template.en.mdx +++ b/web/app/(commonLayout)/datasets/template/template.en.mdx @@ -68,7 +68,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi Processing rules - - mode (string) Cleaning, segmentation mode, automatic / custom + - mode (string) Cleaning, segmentation mode, automatic / custom / hierarchical - rules (object) Custom rules (in automatic mode, this field is empty) - pre_processing_rules (array[object]) Preprocessing rules - id (string) Unique identifier for the preprocessing rule @@ -203,7 +203,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - doc_language In Q&A mode, specify the language of the document, for example: English, Chinese - process_rule Processing rules - - mode (string) Cleaning, segmentation mode, automatic / custom + - mode (string) Cleaning, segmentation mode, automatic / custom / hierarchical - rules (object) Custom rules (in automatic mode, this field is empty) - pre_processing_rules (array[object]) Preprocessing rules - id (string) Unique identifier for the preprocessing rule @@ -314,7 +314,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 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 - high_quality High quality - economy Economy @@ -338,7 +338,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi Embedding model name (optional) - + Embedding model provider name (optional) @@ -783,7 +783,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi Processing rules - - mode (string) Cleaning, segmentation mode, automatic / custom + - mode (string) Cleaning, segmentation mode, automatic / custom / hierarchical - rules (object) Custom rules (in automatic mode, this field is empty) - pre_processing_rules (array[object]) Preprocessing rules - id (string) Unique identifier for the preprocessing rule @@ -885,7 +885,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi Processing rules - - mode (string) Cleaning, segmentation mode, automatic / custom + - mode (string) Cleaning, segmentation mode, automatic / custom / hierarchical - rules (object) Custom rules (in automatic mode, this field is empty) - pre_processing_rules (array[object]) Preprocessing rules - id (string) Unique identifier for the preprocessing rule @@ -1040,10 +1040,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1335,10 +1333,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1620,10 +1616,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` diff --git a/web/app/(commonLayout)/datasets/template/template.ja.mdx b/web/app/(commonLayout)/datasets/template/template.ja.mdx index 6691d902a8..defd48816d 100644 --- a/web/app/(commonLayout)/datasets/template/template.ja.mdx +++ b/web/app/(commonLayout)/datasets/template/template.ja.mdx @@ -337,7 +337,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 埋め込みモデル名(任意) - + 埋め込みモデルのプロバイダ名(任意) @@ -501,7 +501,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```text {{ title: 'Response' }} + ```text {{ title: 'レスポンス' }} 204 No Content ``` @@ -797,10 +797,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'レスポンス' }} + 204 No Content ``` @@ -1092,10 +1090,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'レスポンス' }} + 204 No Content ``` @@ -1377,10 +1373,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'レスポンス' }} + 204 No Content ``` diff --git a/web/app/(commonLayout)/datasets/template/template.zh.mdx b/web/app/(commonLayout)/datasets/template/template.zh.mdx index a8bb7046e6..a2ffac9c3d 100644 --- a/web/app/(commonLayout)/datasets/template/template.zh.mdx +++ b/web/app/(commonLayout)/datasets/template/template.zh.mdx @@ -69,7 +69,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 处理规则 - - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 / hierarchical 父子 - rules (object) 自定义规则(自动模式下,该字段为空) - pre_processing_rules (array[object]) 预处理规则 - id (string) 预处理规则的唯一标识符 @@ -207,7 +207,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - doc_language 在 Q&A 模式下,指定文档的语言,例如:EnglishChinese - process_rule 处理规则 - - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 / hierarchical 父子 - rules (object) 自定义规则(自动模式下,该字段为空) - pre_processing_rules (array[object]) 预处理规则 - id (string) 预处理规则的唯一标识符 @@ -341,7 +341,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi Embedding 模型名称 - + Embedding 模型供应商 @@ -790,7 +790,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 处理规则(选填) - - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 / hierarchical 父子 - rules (object) 自定义规则(自动模式下,该字段为空) - pre_processing_rules (array[object]) 预处理规则 - id (string) 预处理规则的唯一标识符 @@ -892,7 +892,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 处理规则(选填) - - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - mode (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 / hierarchical 父子 - rules (object) 自定义规则(自动模式下,该字段为空) - pre_processing_rules (array[object]) 预处理规则 - id (string) 预处理规则的唯一标识符 @@ -1047,10 +1047,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1342,10 +1340,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1628,10 +1624,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` 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/account/header.tsx b/web/app/account/header.tsx index 2bb89552c8..11b6beec08 100644 --- a/web/app/account/header.tsx +++ b/web/app/account/header.tsx @@ -4,23 +4,25 @@ 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' const Header = () => { const { t } = useTranslation() const router = useRouter() - const back = () => { + const back = useCallback(() => { router.back() - } + }, [router]) + return (
- +
-
-

{t('common.account.account')}

+
+

{t('common.account.account')}

{/* description */} {appDetail.description && ( -
{appDetail.description}
+
{appDetail.description}
)} {/* operations */}
@@ -276,22 +284,50 @@ const AppInfo = ({ expand }: IAppInfoProps) => { {t('app.export')} - { - (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && ( + {appDetail.mode !== 'agent-chat' && + - ) - } + + +
+ { + (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') + &&
{ + setOpen(false) + setShowImportDSLModal(true) + }}> + + {t('workflow.common.importDSL')} +
+ } + { + (appDetail.mode === 'completion' || appDetail.mode === 'chat') + &&
{ + setOpen(false) + setShowSwitchModal(true) + }}> + + {t('app.switch')} +
+ } +
+
+
}
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/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index d4357a0955..b061cbc688 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -76,7 +76,7 @@ const AppPublisher = ({ const appDetail = useAppStore(state => state.appDetail) 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 language = useGetLanguage() diff --git a/web/app/components/app/app-publisher/suggested-action.tsx b/web/app/components/app/app-publisher/suggested-action.tsx index 388fb8bc76..feac9c49fd 100644 --- a/web/app/components/app/app-publisher/suggested-action.tsx +++ b/web/app/components/app/app-publisher/suggested-action.tsx @@ -14,8 +14,8 @@ const SuggestedAction = ({ icon, link, disabled, children, className, ...props } 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', + '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' : 'hover:bg-state-accent-hover hover:text-text-accent cursor-pointer', className, )} {...props} 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..592c95261c 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 @@ -46,8 +46,8 @@ const HistoryPanel: FC = ({
{t('appDebug.feature.conversationHistory.tip')} {t('appDebug.feature.conversationHistory.learnMore')} 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/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 = ({ const [isDeleting, setIsDeleting] = useState(false) return ( -
- { - config.data_source_type === DataSourceType.FILE && ( -
- -
- ) - } - { - config.data_source_type === DataSourceType.NOTION && ( -
- -
- ) - } - { - config.data_source_type === DataSourceType.WEB && ( -
- -
- ) - } -
-
-
{config.name}
- {config.provider === 'external' - ? - : } -
-
-
+
+
+ { + config.data_source_type === DataSourceType.FILE && ( +
+ +
+ ) + } + { + config.data_source_type === DataSourceType.NOTION && ( +
+ +
+ ) + } { - editable &&
setShowSettingsModal(true)} + config.data_source_type === DataSourceType.WEB && ( +
+ +
+ ) + } +
{config.name}
+
+
+ { + editable && { + e.stopPropagation() + setShowSettingsModal(true) + }} > - -
+ + } -
onRemove(config.id)} - onMouseOver={() => setIsDeleting(true)} + state={isDeleting ? ActionButtonState.Destructive : ActionButtonState.Default} + onMouseEnter={() => setIsDeleting(true)} onMouseLeave={() => setIsDeleting(false)} > - -
+ +
+ { + config.indexing_technique && + } + { + config.provider === 'external' && + } setShowSettingsModal(false)} footer={null} mask={isMobile} panelClassName='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'> = ({ {t('appDebug.promptMode.advancedWarning.description')} {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 = () => {
setExpanded(v => !v)} diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index 88bccc95af..6e05bb0209 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -310,17 +310,17 @@ function AppPreview({ mode }: { mode: AppMode }) { 'chat': { title: t('app.types.chatbot'), description: t('app.newApp.chatbotUserDescription'), - link: 'https://docs.dify.ai/guides/application-orchestrate#application_type', + link: 'https://docs.dify.ai/guides/application-orchestrate/readme', }, 'advanced-chat': { title: t('app.types.advanced'), description: t('app.newApp.advancedUserDescription'), - link: 'https://docs.dify.ai/guides/workflow', + link: 'https://docs.dify.ai/en/guides/workflow/README', }, 'agent-chat': { title: t('app.types.agent'), description: t('app.newApp.agentUserDescription'), - link: 'https://docs.dify.ai/guides/application-orchestrate/agent', + link: 'https://docs.dify.ai/en/guides/application-orchestrate/agent', }, 'completion': { title: t('app.newApp.completeApp'), @@ -330,7 +330,7 @@ function AppPreview({ mode }: { mode: AppMode }) { 'workflow': { title: t('app.types.workflow'), description: t('app.newApp.workflowUserDescription'), - link: 'https://docs.dify.ai/guides/workflow', + link: 'https://docs.dify.ai/en/guides/workflow/README', }, } const previewInfo = modeToPreviewInfoMap[mode] 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 && (
-
DSL URL
+
DSL URL
= ({ />
{!file && ( -
+
- -
+ +
{t('datasetCreation.stepOne.uploader.button')} - {t('datasetDocuments.list.batchModal.browse')} + {t('datasetDocuments.list.batchModal.browse')}
{dragging &&
}
)} {file && ( -
+
@@ -126,12 +126,10 @@ const Uploader: FC = ({ {formatFileSize(file.size)}
-
- -
-
+
+ -
+
)} diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 056ce84f1e..7ce164c01b 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -429,6 +429,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { text_to_speech: { enabled: true, }, + questionEditEnable: false, supportAnnotation: true, annotation_reply: { enabled: true, @@ -484,6 +485,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { text_to_speech: { enabled: true, }, + questionEditEnable: false, supportAnnotation: true, annotation_reply: { enabled: true, diff --git a/web/app/components/app/overview/customize/index.tsx b/web/app/components/app/overview/customize/index.tsx index 9e0fadd87d..4e84dd8b1f 100644 --- a/web/app/components/app/overview/customize/index.tsx +++ b/web/app/components/app/overview/customize/index.tsx @@ -103,7 +103,7 @@ const CustomizeModal: FC = ({ 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` + : `${locale.toLowerCase()}/guides/application-publishing/developing-with-apis` }`, '_blank', ) 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..c0a5fdf757 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -241,7 +241,7 @@ const SettingsModal: FC = ({
{t(`${prefixSettings}.modalTip`)} - {t('common.operation.learnMore')} + {t('common.operation.learnMore')}
{/* form body */} 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/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/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/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 = () => { setShowChatSettings(true)} + hideViewChatSettings={inputsForms.length < 1} />
{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 = ({
{t('share.chat.resetChat')}
-
- {t('share.chat.viewChatSettings')} -
+ {!hideViewChatSettings && ( +
+ {t('share.chat.viewChatSettings')} +
+ )}
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..b8d18244e1 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' @@ -112,4 +112,4 @@ const InputsFormContent = ({ showTip }: Props) => { ) } -export default InputsFormContent +export default memo(InputsFormContent) 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..dc4e864728 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,7 +16,7 @@ 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' @@ -141,14 +141,14 @@ const Sidebar = ({ isPanel }: Props) => {
{!appData?.custom_config?.remove_webapp_brand && (
{t('share.chat.poweredBy')}
{appData?.custom_config?.replace_webapp_logo && ( logo )} {!appData?.custom_config?.replace_webapp_logo && ( - + )}
)} 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..bcc3ae628d 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 - + 创意Logo设计 - + - + 科研 科学研究 - + 探索未知的灯塔, 照亮人类前进的道路。 科研,是永不熄灭的好奇心, 也是推动世界进步的引擎。 - + - + 探索 • 创新 • 进步 \`\`\` diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index bff222ea38..fde4674539 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -424,6 +424,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..7c8eb23b1b 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 = ({ item={item} questionIcon={questionIcon} theme={themeBuilder?.theme} + enableEdit={config?.questionEditEnable} switchSibling={switchSibling} /> ) 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..8fc49d1978 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 = ({ item, questionIcon, theme, + enableEdit = true, switchSibling, }) => { const { t } = useTranslation() @@ -50,6 +54,8 @@ const Question: FC = ({ const [isEditing, setIsEditing] = useState(false) const [editedContent, setEditedContent] = useState(content) + const [contentWidth, setContentWidth] = useState(0) + const contentRef = useRef(null) const handleEdit = useCallback(() => { setIsEditing(true) @@ -73,26 +79,44 @@ const Question: FC = ({ 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 ( -
-
+
+
-
+
{ copy(content) Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) }}> - + {enableEdit && - + }
diff --git a/web/app/components/base/chat/chat/type.ts b/web/app/components/base/chat/chat/type.ts index 7f22ba05b7..b37151f8f9 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[] 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 c38a4546dd..c6c02a4d44 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,7 +11,7 @@ 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' export type IHeaderProps = { @@ -36,6 +36,44 @@ const Header: FC = ({ 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 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 (
@@ -51,7 +89,7 @@ const Header: FC = ({ logo )} {!appData?.custom_config?.replace_webapp_logo && ( - + )}
)} @@ -59,6 +97,21 @@ const Header: FC = ({ {currentConversationId && ( )} + { + showToggleExpandButton && ( + + + { + expanded + ? + : + } + + + ) + } {currentConversationId && allowResetChat && ( = ({ return (
{customerIcon} @@ -91,6 +144,21 @@ const Header: FC = ({
+ { + showToggleExpandButton && ( + + + { + expanded + ? + : + } + + + ) + } {currentConversationId && allowResetChat && ( { @@ -118,7 +118,7 @@ const Chatbot = () => { logo )} {!appData?.custom_config?.replace_webapp_logo && ( - + )}
)} 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..e56520d23f 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' @@ -112,4 +112,4 @@ const InputsFormContent = ({ showTip }: Props) => { ) } -export default InputsFormContent +export default memo(InputsFormContent) 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 & { supportAnnotation?: boolean appId?: string + questionEditEnable?: boolean supportFeedback?: boolean supportCitationHitInfo?: boolean } 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/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) }} > 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 = ({ }
{ - showDownloadAction && tmp_preview_url && ( + showDownloadAction && download_url && ( { e.stopPropagation() - downloadFile(tmp_preview_url || '', name) + downloadFile(download_url || '', name) }} > 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/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 @@ + + + + + + + + + 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/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/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/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/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 & { + ref?: React.RefObject>; + }, +) => + +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 = ({ { hovering => (
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 (
) 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>(( @@ -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 = ({
) } -
+
{ options.map((option, index) => ( 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 ( -
+
{children} 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 = ({ : (
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/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 + case 'dark': return + default: return + } + } + + return ( + + setOpen(!open)} + > + + {getCurrentIcon()} + + + +
+ + + +
+
+
+ ) +} 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 ( +
+
handleThemeChange('system')} + > +
+ +
+
+
+
handleThemeChange('light')} + > +
+ +
+
+
+
handleThemeChange('dark')} + > +
+ +
+
+
+ ) +} 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/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/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 = ({ 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(
= ({
- {t('billing.plansCommon.comparePlanAndFeatures')} + {t('billing.plansCommon.comparePlanAndFeatures')}
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..444df98f24 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' @@ -246,7 +246,7 @@ const CustomWebAppBrand = () => {
POWERED BY
{webappLogo ? logo - : + : } )} @@ -305,7 +305,7 @@ const CustomWebAppBrand = () => {
POWERED BY
{webappLogo ? logo - : + : } )} 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 => { + 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-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/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 = ({ onConfig, + provider, }) => { const { t } = useTranslation() @@ -38,7 +39,7 @@ const NoData: FC = ({ } : 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/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/index.tsx b/web/app/components/datasets/documents/index.tsx index 854c984559..32980eea1e 100644 --- a/web/app/components/datasets/documents/index.tsx +++ b/web/app/components/datasets/documents/index.tsx @@ -264,8 +264,8 @@ const Documents: FC = ({ datasetId }) => { 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' + ? 'https://docs.dify.ai/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' + : 'https://docs.dify.ai/en/guides/knowledge-base/integrate-knowledge-within-application' } > {t('datasetDocuments.list.learnMore')} 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 = ({ api return ( <> -
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 = ({ items, value, onS return (
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..5c3c1261b6 100644 --- a/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx @@ -16,12 +16,12 @@ const InfoPanel = () => { {t('dataset.connectDatasetIntro.content.front')} - + {t('dataset.connectDatasetIntro.content.link')} {t('dataset.connectDatasetIntro.content.end')} - + {t('dataset.connectDatasetIntro.learnMore')}

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..5fbddea06b 100644 --- a/web/app/components/datasets/external-knowledge-base/create/index.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/index.tsx @@ -59,7 +59,7 @@ const ExternalKnowledgeBaseCreate: React.FC = {t('dataset.connectHelper.helper1')} {t('dataset.connectHelper.helper2')} {t('dataset.connectHelper.helper3')} - + {t('dataset.connectHelper.helper4')} {t('dataset.connectHelper.helper5')} 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 = ({ {trigger} - setOpen(false)} /> + setOpen(false)} onBack={() => setOpen(false)} /> 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 = ({ onSave={handleSave} hasBack onBack={() => setStep(Step.select)} + onClose={() => setStep(Step.select)} /> )} diff --git a/web/app/components/develop/doc.tsx b/web/app/components/develop/doc.tsx index 12a273ea9c..c61cc09863 100644 --- a/web/app/components/develop/doc.tsx +++ b/web/app/components/develop/doc.tsx @@ -121,7 +121,7 @@ const Doc = ({ appDetail }: IDocProps) => { )}
-
+
{(appDetail?.mode === 'chat' || appDetail?.mode === 'agent-chat') && ( (() => { switch (locale) { 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} + {value}
-
- -
-
{ - copy(value) - setIsCopied(true) - }}> -
-
-
+
+
) 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..9682b51b39 100755 --- a/web/app/components/develop/template/template.en.mdx +++ b/web/app/components/develop/template/template.en.mdx @@ -248,7 +248,7 @@ The text generation application offers non-session support and is ideal for tran ### Request Example - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -383,6 +383,69 @@ The text generation application offers non-session support and is ideal for tran --- + + + + Get application's feedbacks. + + ### Query + + + (optional)pagination,default:1 + + + + + + (optional) records per page default:20 + + + + ### Response + - `data` (List) return apps feedback list. + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + +--- + +--- + + + + + 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 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template.ja.mdx b/web/app/components/develop/template/template.ja.mdx index 50bf1e234b..5380d4da91 100755 --- a/web/app/components/develop/template/template.ja.mdx +++ b/web/app/components/develop/template/template.ja.mdx @@ -247,7 +247,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### リクエスト例 - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -381,6 +381,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from --- + + + + アプリのエンドユーザーからのフィードバックや「いいね」を取得します。 + + ### クエリ + + + (任意)ページ番号。デフォルト値:1 + + + + + + (任意)1ページあたりの件数。デフォルト値:20 + + + + ### レスポンス + - `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。 + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + +--- + +--- + + + + + アプリの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のアイコンをチャット内の🤖に置き換えるかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template.zh.mdx b/web/app/components/develop/template/template.zh.mdx index 447dc08396..8fa0776ee3 100755 --- a/web/app/components/develop/template/template.zh.mdx +++ b/web/app/components/develop/template/template.zh.mdx @@ -226,7 +226,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -355,6 +355,68 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' +--- + + + + Get application's feedbacks. + + ### Query + + + (optional)pagination,default:1 + + + + + + (optional) records per page default:20 + + + + ### Response + - `data` (List) return apps feedback list. + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + --- - --- --- + + + + 用于获取应用的 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 图标替换聊天中的 🤖 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ + - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` 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 0d10af6a71..6c80c2f787 100644 --- a/web/app/components/develop/template/template_advanced_chat.en.mdx +++ b/web/app/components/develop/template/template_advanced_chat.en.mdx @@ -352,7 +352,7 @@ Chat applications support session persistence, allowing previous chat history to ### Request Example - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -487,6 +487,69 @@ Chat applications support session persistence, allowing previous chat history to --- + + + + Get application's feedbacks. + + ### Query + + + (optional)pagination,default:1 + + + + + + (optional) records per page default:20 + + + + ### Response + - `data` (List) return apps feedback list. + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + +--- + - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1104,6 +1165,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 @@ -1159,6 +1227,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 }, @@ -1244,6 +1318,64 @@ Chat applications support session persistence, allowing previous chat history to --- + + + + 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 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ + - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` 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 db869f2093..65e140e9a6 100644 --- a/web/app/components/develop/template/template_advanced_chat.ja.mdx +++ b/web/app/components/develop/template/template_advanced_chat.ja.mdx @@ -352,7 +352,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### リクエスト例 - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -487,6 +487,70 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from --- + + + + アプリのエンドユーザーからのフィードバックや「いいね」を取得します。 + + ### クエリ + + + (任意)ページ番号。デフォルト値:1 + + + + + + (任意)1ページあたりの件数。デフォルト値:20 + + + + ### レスポンス + - `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。 + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + +--- + + - ```json {{ title: '応答' }} - { - "result": "success" - } + ```text {{ title: '応答' }} + 204 No Content ``` @@ -1103,6 +1165,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) 注釈返信 @@ -1158,6 +1227,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 }, @@ -1241,3 +1316,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from + +--- + + + + + アプリの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のアイコンをチャット内の🤖に置き換えるかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ 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 e634130a4b..427402fd60 100755 --- a/web/app/components/develop/template/template_advanced_chat.zh.mdx +++ b/web/app/components/develop/template/template_advanced_chat.zh.mdx @@ -362,7 +362,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -493,6 +493,71 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- + + + + 获取应用的终端用户反馈、点赞。 + + ### Query + + + (选填)分页,默认值:1 + + + + + + (选填)每页数量,默认值:20 + + + + ### Response + - `data` (List) 返回该APP的点赞、反馈列表。 + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + + ``` + + + + +--- + - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1136,6 +1199,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) 标记回复 @@ -1266,7 +1336,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- ---- + + + + 用于获取应用的 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 图标替换聊天中的 🤖 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` diff --git a/web/app/components/develop/template/template_chat.en.mdx b/web/app/components/develop/template/template_chat.en.mdx index aa3b9c62e5..8a0b3edeec 100644 --- a/web/app/components/develop/template/template_chat.en.mdx +++ b/web/app/components/develop/template/template_chat.en.mdx @@ -315,7 +315,7 @@ Chat applications support session persistence, allowing previous chat history to ### Request Example - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -450,6 +450,69 @@ Chat applications support session persistence, allowing previous chat history to --- + + + + Get application's feedbacks. + + ### Query + + + (optional)pagination,default:1 + + + + + + (optional) records per page default:20 + + + + ### Response + - `data` (List) return apps feedback list. + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + +--- + - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1140,6 +1201,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 @@ -1195,6 +1263,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 }, @@ -1280,6 +1354,64 @@ Chat applications support session persistence, allowing previous chat history to --- + + + + 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 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ + - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` diff --git a/web/app/components/develop/template/template_chat.ja.mdx b/web/app/components/develop/template/template_chat.ja.mdx index 7ad9a7fe3e..0adf133767 100644 --- a/web/app/components/develop/template/template_chat.ja.mdx +++ b/web/app/components/develop/template/template_chat.ja.mdx @@ -315,7 +315,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### リクエスト例 - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -450,6 +450,70 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from --- + + + + アプリのエンドユーザーからのフィードバックや「いいね」を取得します。 + + ### クエリ + + + (任意)ページ番号。デフォルト値:1 + + + + + + (任意)1ページあたりの件数。デフォルト値:20 + + + + ### レスポンス + - `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。 + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + +--- + + - ```json {{ title: '応答' }} - { - "result": "success" - } + ```text {{ title: '応答' }} + 204 No Content ``` @@ -1130,6 +1192,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) 注釈返信 @@ -1185,6 +1254,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 }, @@ -1268,3 +1343,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from + +--- + + + + + アプリの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のアイコンをチャット内の🤖に置き換えるかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template_chat.zh.mdx b/web/app/components/develop/template/template_chat.zh.mdx index a49f70a514..7287227296 100644 --- a/web/app/components/develop/template/template_chat.zh.mdx +++ b/web/app/components/develop/template/template_chat.zh.mdx @@ -333,7 +333,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -464,6 +464,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- + + + + 获取应用的终端用户反馈、点赞。 + + ### Query + + + (选填)分页,默认值:1 + + + + + + (选填)每页数量,默认值:20 + + + + ### Response + - `data` (List) 返回该APP的点赞、反馈列表。 + + + + + + ```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' + ``` + + + + + ```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" + } + ] + } + ``` + + + +--- + - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` @@ -1143,6 +1204,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) 标记回复 @@ -1271,3 +1339,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' + +--- + + + + + 用于获取应用的 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 图标替换聊天中的 🤖 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template_workflow.en.mdx b/web/app/components/develop/template/template_workflow.en.mdx index 0f15c4406b..95e4971e67 100644 --- a/web/app/components/develop/template/template_workflow.en.mdx +++ b/web/app/components/develop/template/template_workflow.en.mdx @@ -90,7 +90,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: ```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 ``` The structure of the streaming chunks varies depending on the `event`: @@ -116,6 +116,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 @@ -469,7 +476,7 @@ Workflow applications offers non-session support and is ideal for translation, a ### Request Example - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -730,3 +737,56 @@ Workflow applications offers non-session support and is ideal for translation, a +--- + + + + + 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 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template_workflow.ja.mdx b/web/app/components/develop/template/template_workflow.ja.mdx index 0239b40224..3ab286d28a 100644 --- a/web/app/components/develop/template/template_workflow.ja.mdx +++ b/web/app/components/develop/template/template_workflow.ja.mdx @@ -93,7 +93,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from 各ストリーミングチャンクは`data:`で始まり、2つの改行文字`\n\n`で区切られます。以下のように表示されます: ```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 ``` ストリーミングチャンクの構造は`event`に応じて異なります: @@ -119,6 +119,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `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 @@ -472,7 +479,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### リクエスト例 - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -733,3 +740,57 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from +——— + + + + + アプリの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) ワークフローの詳細を表示するかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ + diff --git a/web/app/components/develop/template/template_workflow.zh.mdx b/web/app/components/develop/template/template_workflow.zh.mdx index 939df2703d..0cc3dab79e 100644 --- a/web/app/components/develop/template/template_workflow.zh.mdx +++ b/web/app/components/develop/template/template_workflow.zh.mdx @@ -87,7 +87,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 每个流式块均为 data: 开头,块之间以 `\n\n` 即两个换行符分隔,如下所示: ```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 ``` 流式块中根据 `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 @@ -463,7 +470,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -720,3 +727,57 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 + +--- + + + + + 用于获取应用的 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) 是否显示工作流详情 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```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, + } + ``` + + + +___ 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 = ({ 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 = ({ className={itemClassName(name === value)} onClick={() => onChange(name)} > - {categoryI18n[name] ? t(`explore.category.${name}`) : name} + {(categoryI18n as any)[name] ? t(`explore.category.${name}`) : name}
))}
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..6129b48dce 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -7,7 +7,7 @@ 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' type IAccountSettingProps = { @@ -28,21 +28,21 @@ export default function AccountAbout({ onClose={noop} className='!w-[480px] !max-w-[480px] !px-6 !py-4' > -
-
+
+
-
- -
Version {langeniusVersionInfo?.current_version}
-
+
+ +
Version {langeniusVersionInfo?.current_version}
+
© {dayjs().year()} LangGenius, Inc., Contributors.
{ IS_CE_EDITION ? Open Source License : <> - Privacy Policy, + Privacy Policy,  Terms of Service } @@ -51,7 +51,7 @@ export default function AccountAbout({
-
+
{ isLatest ? t('common.about.latestAvailable', { version: langeniusVersionInfo.latest_version }) @@ -59,7 +59,7 @@ export default function AccountAbout({ }
-
+ +
+
+ +
{t('common.theme.theme')}
+ +
+
+
handleLogout()}>
{ 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/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..2426f7b933 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 = ({ -
{basePath + value.url}
+
{value.url}
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 = ({
{t('common.modelProvider.discoverMore')} - + {t('plugin.marketplace.difyMarketplace')} 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..c2345fbdaa 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' @@ -49,7 +49,7 @@ const Header = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedSegment]) return ( -
+
{isMobile &&
{
} { !isMobile - &&
- - + &&
+ +
/
@@ -76,7 +76,7 @@ const Header = () => { {isMobile && (
- +
/
{enableBilling ? : } @@ -84,7 +84,7 @@ const Header = () => { )} { !isMobile && ( -
+
{!isCurrentWorkspaceDatasetOperator && } {!isCurrentWorkspaceDatasetOperator && } {(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && } @@ -92,7 +92,7 @@ const Header = () => {
) } -
+
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 }:
{ navs.map(nav => ( - -
{ + +
{ if (curNav?.id === nav.id) return setAppDetail() @@ -112,21 +112,21 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: {nav.name}
-
+ )) }
{!isApp && isCurrentWorkspaceEditor && ( - +
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 ', )}> -
- +
+
-
{createText}
+
{createText}
- + )} {isApp && isCurrentWorkspaceEditor && ( @@ -134,14 +134,14 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: <>
-
- +
+
-
{createText}
- +
{createText}
+
-
onCreate('blank')}> - +
onCreate('blank')}> + {t('app.newApp.startFromBlank')}
-
onCreate('template')}> - +
onCreate('template')}> + {t('app.newApp.startFromTemplate')}
-
-
onCreate('dsl')}> - +
+
onCreate('dsl')}> + {t('app.importDSL')}
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/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')} - +
- {data.declaration.endpoints.map((endpoint, index) => ( + {data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => (
{endpoint.method}
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 = ({ <> {isString && ( = ({ plugin, }) => { const { t } = useTranslation() + const { theme } = useTheme() const { categoriesMap } = useSingleCategories() const currentPluginID = usePluginPageContext(v => v.currentPluginID) const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) @@ -164,7 +166,7 @@ const PluginItem: FC = ({ } {source === PluginSource.marketplace && <> - +
{t('plugin.from')} 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 = () => { <>
{t(`${i18nPrefix}.title`)} - + {t(`${i18nPrefix}.viewDocs`)} diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx index 7fee823c02..53a00dc01c 100644 --- a/web/app/components/plugins/plugin-page/empty/index.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.tsx @@ -47,14 +47,14 @@ const Empty = () => { ))}
{/* mask */} -
+
- - + +
diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index a4bc7a6e6c..cb57b7f4c6 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -34,10 +34,10 @@ 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' @@ -187,7 +187,7 @@ const PluginPage = ({ isExploringMarketplace && ( <>
)} @@ -657,7 +664,7 @@ const TextGeneration: FC = ({ showResultPanel() }} > -
+
)} {renderResWrap} diff --git a/web/app/components/share/text-generation/menu-dropdown.tsx b/web/app/components/share/text-generation/menu-dropdown.tsx index 91605f6a9a..d038b94c1a 100644 --- a/web/app/components/share/text-generation/menu-dropdown.tsx +++ b/web/app/components/share/text-generation/menu-dropdown.tsx @@ -12,6 +12,8 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import Divider from '@/app/components/base/divider' +import ThemeSwitcher from '@/app/components/base/theme-switcher' import InfoModal from './info-modal' import type { SiteInfo } from '@/models/share' import cn from '@/utils/classnames' @@ -59,6 +61,13 @@ const MenuDropdown: FC = ({
+
+
+
{t('common.theme.theme')}
+ +
+
+
{data?.privacy_policy && ( 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 = ({ const onClear = () => { const newInputs: Record = {} 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 = ({ useEffect(() => { const newInputs: Record = {} 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/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 = ({ {t('common.operation.in')} diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index d1144d7e69..b1da6d5611 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -53,7 +53,10 @@ const ProviderList = () => { }) }, [activeTab, tagFilterValue, keywords, collectionList]) - const [currentProvider, setCurrentProvider] = useState() + const [currentProviderId, setCurrentProviderId] = useState() + const currentProvider = useMemo(() => { + 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 = () => { >
{ setActiveTab(state) if (state !== activeTab) - setCurrentProvider(undefined) + setCurrentProviderId(undefined) }} options={options} /> @@ -102,12 +105,12 @@ const ProviderList = () => { {filteredCollectionList.map(collection => (
setCurrentProvider(collection)} + onClick={() => setCurrentProviderId(collection.id)} > { {currentProvider && !currentProvider.plugin_id && ( setCurrentProvider(undefined)} + onHide={() => setCurrentProviderId(undefined)} onRefreshData={refetch} /> )} 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..6dd268cb3a 100644 --- a/web/app/components/tools/provider/custom-create-card.tsx +++ b/web/app/components/tools/provider/custom-create-card.tsx @@ -28,7 +28,7 @@ const Contribute = ({ onRefreshData }: Props) => { 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 'https://docs.dify.ai/en/guides/tools#how-to-create-custom-tools' }, [language]) const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) 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/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)) + 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/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 = ({ : (
= ({ 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 = ({ 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 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)) - 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 64aefc4dd3..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 = 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 = memo(({
= memo(({ nodesConnectable={!nodesReadOnly} nodesFocusable={!nodesReadOnly} edgesFocusable={!nodesReadOnly} - panOnScroll + 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..de23602e34 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -66,6 +66,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 +78,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' @@ -223,7 +226,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { {t('workflow.nodes.agent.learnMore')} 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..51969f8510 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 @@ -34,7 +34,7 @@ const DefaultValue = ({ {t('workflow.nodes.common.errorHandle.defaultValue.desc')}   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/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/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 = ({ {schema.type === FormTypeEnum.textNumber && ( { + 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,9 +624,9 @@ const getIterationItemType = ({ arrayType = curr.find((v: any) => v.variable === (valueSelector).join('.'))?.type } else { - for (let i = 1; i < valueSelector.length - 1; i++) { + for (let i = 1; i < valueSelector.length; i++) { const key = valueSelector[i] - const isLast = i === valueSelector.length - 2 + const isLast = i === valueSelector.length - 1 curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : [] if (isLast) 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..daad6ffcc0 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 @@ -8,7 +8,7 @@ export const useNodeHelpLink = (nodeType: BlockEnum) => { if (language === 'zh_Hans') return 'https://docs.dify.ai/zh-hans/guides/workflow/node/' - return 'https://docs.dify.ai/guides/workflow/node/' + return 'https://docs.dify.ai/en/guides/workflow/node/' }, [language]) const linkMap = useMemo(() => { if (language === 'zh_Hans') { diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index 7fd6314781..f92e92dbcb 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -102,7 +102,6 @@ const AgentPanel: FC> = (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) => { 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 = ({ />
{item.operation !== WriteMode.clear && item.operation !== WriteMode.set + && item.operation !== WriteMode.removeFirst && item.operation !== WriteMode.removeLast && !writeModeTypesNum?.includes(item.operation) && ( = { 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/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> = ({ isChatMode, }) return ( -
+
{!isEnv && !isChatVar && ( <> 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 = ({ }, [onRemove]) return ( -
-
+
-
{currentMetadata?.name}
+
{currentMetadata?.name}
{currentMetadata?.type}
diff --git a/web/app/components/workflow/nodes/list-operator/panel.tsx b/web/app/components/workflow/nodes/list-operator/panel.tsx index a09839f04f..d93a79397d 100644 --- a/web/app/components/workflow/nodes/list-operator/panel.tsx +++ b/web/app/components/workflow/nodes/list-operator/panel.tsx @@ -97,16 +97,14 @@ const Panel: FC> = ({ {inputs.extract_by?.enabled ? (
- {hasSubVariable && ( -
- -
- )} +
+ +
) : null} 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..344d02c011 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 @@ -49,7 +49,7 @@ const DEFAULT_SCHEMA: SchemaRoot = { 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', + en_US: 'https://docs.dify.ai/en/guides/workflow/structured-outputs', ja_JP: 'https://docs.dify.ai/ja-jp/guides/workflow/structured-outputs', } 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 = ({ return ( = ({
{isString && ( { - 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 ( - - - -
- -
-
-
- -
-
-
handleExportImage('png')} - > - {t('workflow.common.exportPNG')} -
-
handleExportImage('jpeg')} - > - {t('workflow.common.exportJPEG')} -
-
handleExportImage('svg')} - > - {t('workflow.common.exportSVG')} -
-
-
-
-
- ) -} - -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 ( + + + +
+ +
+
+
+ +
+
+
handleExportImage('png')} + > + {t('workflow.common.exportPNG')} +
+
handleExportImage('jpeg')} + > + {t('workflow.common.exportJPEG')} +
+
handleExportImage('svg')} + > + {t('workflow.common.exportSVG')} +
+
+
+
+
+ ) +} + +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 = () => {
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 ( -
+
{t('workflow.versionHistory.title')}
{
-
- {(isFetching && !versionHistory?.pages?.length) - ? ( - - ) - : ( - <> - {versionHistory?.pages?.map((page, pageNumber) => ( - page.items?.map((item, idx) => { - const isLast = pageNumber === versionHistory.pages.length - 1 && idx === page.items.length - 1 - return - }) - ))} - {hasNextPage && ( -
-
-
- { - isFetching - ? - : } -
-
- {t('workflow.common.loadMore')} -
-
-
- )} - {!isFetching && (!versionHistory?.pages?.length || !versionHistory.pages[0].items.length) && ( - - )} - - )} +
+
+ {(isFetching && !versionHistory?.pages?.length) + ? ( + + ) + : ( + <> + {versionHistory?.pages?.map((page, pageNumber) => ( + page.items?.map((item, idx) => { + const isLast = pageNumber === versionHistory.pages.length - 1 && idx === page.items.length - 1 + return + }) + ))} + {!isFetching && (!versionHistory?.pages?.length || !versionHistory.pages[0].items.length) && ( + + )} + + )} +
+ {hasNextPage && ( +
+
+
+ {isFetching + ? + : } +
+
+ {t('workflow.common.loadMore')} +
+
+
+ )}
{restoreConfirmOpen && ( 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 = 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) => { + 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) => { - e.stopPropagation() - e.nativeEvent.stopImmediatePropagation() - onShowIterationResultList(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {}) - } return (
-
- dify logo +
+
diff --git a/web/app/install/installForm.tsx b/web/app/install/installForm.tsx index 99f2ec6b77..c01be722c0 100644 --- a/web/app/install/installForm.tsx +++ b/web/app/install/installForm.tsx @@ -16,7 +16,6 @@ import Button from '@/app/components/base/button' import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common' import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common' -import { basePath } from '@/utils/var' const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/ @@ -81,12 +80,12 @@ const InstallForm = () => { fetchSetupStatus().then((res: SetupStatusResponse) => { if (res.step === 'finished') { localStorage.setItem('setup_status', 'finished') - router.push(`${basePath}/signin`) + router.push('/signin') } else { fetchInitValidateStatus().then((res: InitValidateStatusResponse) => { if (res.status === 'not_started') - router.push(`${basePath}/init`) + router.push('/init') }) } setLoading(false) diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 438f56d363..dc4f540c0d 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -63,8 +63,7 @@ const LocaleLayout = async ({ 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/signin/_header.tsx b/web/app/signin/_header.tsx index c7b1e67092..5e85a8d306 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -2,25 +2,41 @@ 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' + +// Avoid rendering the logo and theme selector on the server +const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), { + ssr: false, + loading: () =>
, +}) +const ThemeSelector = dynamic(() => import('@/app/components/base/theme-selector'), { + ssr: false, + loading: () =>
, +}) const Header = () => { const { locale, setLocaleOnClient } = useContext(I18n) - return
- - item.supported)} + onChange={(value) => { + setLocaleOnClient(value as Locale) + }} + /> + + +
+
+ ) } export default Header diff --git a/web/app/signin/oneMoreStep.tsx b/web/app/signin/oneMoreStep.tsx index a78a3ab3b1..7a326a13de 100644 --- a/web/app/signin/oneMoreStep.tsx +++ b/web/app/signin/oneMoreStep.tsx @@ -164,7 +164,7 @@ const OneMoreStep = () => { {t('login.license.link')}
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/context/i18n.ts b/web/context/i18n.ts index 6db211dd5d..463e01d1c5 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,15 @@ export const useGetLanguage = () => { return getLanguage(locale) } +export const useGetDocLanguage = () => { + const { locale } = useI18N() + + return getDocLanguage(locale) +} +export const useGetPricingPageLanguage = () => { + const { locale } = useI18N() + + return getPricingPageLanguage(locale) +} export default I18NContext 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/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 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 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.ts b/web/i18n/de-DE/app.ts index 25fddf7a91..d9454a2a4c 100644 --- a/web/i18n/de-DE/app.ts +++ b/web/i18n/de-DE/app.ts @@ -161,6 +161,10 @@ 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', @@ -201,6 +205,17 @@ 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', + }, } 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..54bde0b67e 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', @@ -153,6 +157,9 @@ const translation = { community: 'Gemeinschaft', about: 'Über', logout: 'Abmelden', + compliance: 'Einhaltung', + support: 'Unterstützung', + github: 'GitHub', }, settings: { accountGroup: 'KONTO', @@ -202,6 +209,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 +465,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', @@ -543,6 +553,7 @@ const translation = { inputPlaceholder: 'Sprechen Sie mit dem Bot', 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', @@ -637,6 +648,25 @@ const translation = { 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', + }, } export default translation diff --git a/web/i18n/de-DE/custom.ts b/web/i18n/de-DE/custom.ts index 2f4cabd67d..e86e0c2cd4 100644 --- a/web/i18n/de-DE/custom.ts +++ b/web/i18n/de-DE/custom.ts @@ -3,6 +3,8 @@ 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', 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/plugin.ts b/web/i18n/de-DE/plugin.ts index 64c59fd79f..9202c224de 100644 --- a/web/i18n/de-DE/plugin.ts +++ b/web/i18n/de-DE/plugin.ts @@ -180,6 +180,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', @@ -204,6 +206,10 @@ 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}}', } export default translation diff --git a/web/i18n/de-DE/share-app.ts b/web/i18n/de-DE/share-app.ts index 5ea67dd08f..462286fa23 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/de-DE/workflow.ts index 74bea2b85e..e229506706 100644 --- a/web/i18n/de-DE/workflow.ts +++ b/web/i18n/de-DE/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Umgebungsvariablen', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Testlaufiteration', back: 'Zurück', iteration: 'Iteration', + loop: 'Schleife', }, tabs: { 'searchBlock': 'Block suchen', @@ -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', @@ -404,6 +419,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 +459,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 +575,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 +592,7 @@ const translation = { }, select: 'Auswählen', addSubVariable: 'Untervariable', + condition: 'Bedingung', }, variableAssigner: { title: 'Variablen zuweisen', @@ -562,6 +635,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', @@ -766,6 +841,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 +884,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/common.ts b/web/i18n/en-US/common.ts index bf2bf83f68..b194f6ed15 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', @@ -475,7 +482,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', 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-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..ea125a1332 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', @@ -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/share-app.ts b/web/i18n/en-US/share-app.ts index 3db0e98f99..bf99005d71 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: { diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 543f689611..f7121a7a8a 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -638,6 +638,8 @@ const translation = { 'clear': 'Clear', 'extend': 'Extend', 'append': 'Append', + 'remove-first': 'Remove First', + 'remove-last': 'Remove Last', '+=': '+=', '-=': '-=', '*=': '*=', @@ -736,6 +738,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.ts b/web/i18n/es-ES/app.ts index f6cc9d1735..3d2a39db97 100644 --- a/web/i18n/es-ES/app.ts +++ b/web/i18n/es-ES/app.ts @@ -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,16 @@ 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', + }, } 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..2c9e69320a 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', @@ -157,6 +161,9 @@ const translation = { community: 'Comunidad', about: 'Acerca de', logout: 'Cerrar sesión', + support: 'Apoyo', + compliance: 'Cumplimiento', + github: 'GitHub', }, settings: { accountGroup: 'CUENTA', @@ -206,6 +213,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 +469,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', @@ -547,6 +557,7 @@ const translation = { inputPlaceholder: 'Hablar con el bot', 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 +648,24 @@ const translation = { pagination: { perPage: 'Elementos por página', }, + theme: { + auto: 'sistema', + light: 'luz', + theme: 'Tema', + }, + 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', + }, } export default translation diff --git a/web/i18n/es-ES/custom.ts b/web/i18n/es-ES/custom.ts index 0dd6512589..a3dcddef08 100644 --- a/web/i18n/es-ES/custom.ts +++ b/web/i18n/es-ES/custom.ts @@ -3,6 +3,8 @@ 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', 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/plugin.ts b/web/i18n/es-ES/plugin.ts index 9453c20f97..3e62e185c3 100644 --- a/web/i18n/es-ES/plugin.ts +++ b/web/i18n/es-ES/plugin.ts @@ -180,6 +180,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.', @@ -204,6 +206,10 @@ 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}}', } export default translation diff --git a/web/i18n/es-ES/share-app.ts b/web/i18n/es-ES/share-app.ts index b1ac171389..41aa35c43e 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/es-ES/workflow.ts index 0ee1b0a223..5a2d02bc0d 100644 --- a/web/i18n/es-ES/workflow.ts +++ b/web/i18n/es-ES/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Variables de Entorno', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Iteración de ejecución de prueba', back: 'Atrás', iteration: 'Iteración', + loop: 'Bucle', }, tabs: { 'searchBlock': 'Buscar bloque', @@ -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', @@ -404,6 +419,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 +459,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 +573,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 +590,7 @@ const translation = { }, select: 'Escoger', addSubVariable: 'Sub Variable', + condition: 'Condición', }, variableAssigner: { title: 'Asignar variables', @@ -562,6 +633,8 @@ const translation = { 'overwrite': 'Sobrescribir', '/=': '/=', 'set': 'Poner', + 'remove-last': 'Eliminar último', + 'remove-first': 'Eliminar primero', }, 'variables': 'Variables', 'setParameter': 'Establecer parámetro...', @@ -769,6 +842,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 +884,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.ts b/web/i18n/fa-IR/app.ts index 70e7801810..5f10269e2a 100644 --- a/web/i18n/fa-IR/app.ts +++ b/web/i18n/fa-IR/app.ts @@ -163,6 +163,10 @@ const translation = { title: 'اوپیک', description: 'Opik یک پلت فرم منبع باز برای ارزیابی، آزمایش و نظارت بر برنامه های LLM است.', }, + weave: { + title: 'بافندگی', + description: 'ویو یک پلتفرم متن باز برای ارزیابی، آزمایش و نظارت بر برنامه‌های LLM است.', + }, }, answerIcon: { descriptionInExplore: 'آیا از نماد WebApp برای جایگزینی 🤖 در Explore استفاده کنیم یا خیر', @@ -194,6 +198,17 @@ const translation = { label: 'برنامه', placeholder: 'برنامه ای را انتخاب کنید...', }, + structOutput: { + required: 'ضروری', + modelNotSupported: 'مدل پشتیبانی نمی شود', + notConfiguredTip: 'خروجی ساختاری هنوز تنظیم نشده است', + structured: 'ساختار یافته', + configure: 'تنظیمات', + moreFillTip: 'نمایش حداکثر ۱۰ سطح تو در تو', + LLMResponse: 'پاسخ مدل زبان بزرگ', + modelNotSupportedTip: 'مدل فعلی این ویژگی را پشتیبانی نمی‌کند و به‌طور خودکار به تزریق درخواست تنزل پیدا می‌کند.', + structuredTip: 'خروجی‌های ساختاری یک ویژگی است که تضمین می‌کند مدل همیشه پاسخ‌هایی تولید می‌کند که به طرح JSON ارائه شده شما پایبند باشد.', + }, } 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..64a5c3b73d 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}} الزامی است', @@ -157,6 +161,9 @@ const translation = { community: 'انجمن', about: 'درباره', logout: 'خروج', + github: 'گیت‌هاب', + compliance: 'انطباق', + support: 'پشتیبانی', }, settings: { accountGroup: 'حساب کاربری', @@ -206,6 +213,9 @@ const translation = { deleteSuccessTip: 'حساب شما برای پایان دادن به حذف به زمان نیاز دارد. وقتی همه چیز تمام شد به شما ایمیل خواهیم زد.', deletePrivacyLinkTip: 'برای کسب اطلاعات بیشتر در مورد نحوه مدیریت داده های شما، لطفا به ما مراجعه کنید', feedbackLabel: 'به ما بگویید چرا حساب خود را حذف کرده اید؟', + editWorkspaceInfo: 'ویرایش اطلاعات فضای کار', + workspaceName: 'نام فضای کاری', + workspaceIcon: 'آیکون محیط کار', }, members: { team: 'تیم', @@ -459,7 +469,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 +557,7 @@ const translation = { inputPlaceholder: 'با ربات صحبت کنید', thought: 'فکر', thinking: 'تفکر...', + resend: 'دوباره ارسال کنید', }, promptEditor: { placeholder: 'دستور خود را اینجا بنویسید، «{» را وارد کنید تا یک متغیر درج کنید، «/» را وارد کنید تا یک بلوک محتوای دستور درج کنید', @@ -637,6 +648,25 @@ const translation = { 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: 'مرورگر', + }, } 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: '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 = { 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/plugin.ts b/web/i18n/fa-IR/plugin.ts index 5ecde0ed57..81aa61ae84 100644 --- a/web/i18n/fa-IR/plugin.ts +++ b/web/i18n/fa-IR/plugin.ts @@ -180,6 +180,8 @@ const translation = { difyMarketplace: 'بازار دیفی', empower: 'توسعه هوش مصنوعی خود را توانمند کنید', discover: 'کشف', + verifiedTip: 'تأیید شده توسط دیفی', + partnerTip: 'تأیید شده توسط یک شریک دیفی', }, task: { installing: 'نصب پلاگین های {{installingLength}}، 0 انجام شد.', @@ -204,6 +206,10 @@ const translation = { installAction: 'نصب', allCategories: 'همه دسته بندی ها', search: 'جستجو', + metadata: { + title: 'پلاگین ها', + }, + difyVersionNotCompatible: 'نسخه فعلی دیفی با این پلاگین سازگار نیست، لطفاً به نسخه حداقل مورد نیاز به‌روزرسانی کنید: {{minimalDifyVersion}}', } export default translation diff --git a/web/i18n/fa-IR/share-app.ts b/web/i18n/fa-IR/share-app.ts index f3f1360a92..bf1c0dec50 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,8 @@ const translation = { moreThanMaxLengthLine: 'ردیف {{rowIndex}}: مقدار {{varName}} نمی‌تواند بیشتر از {{maxLength}} کاراکتر باشد', atLeastOne: 'لطفاً حداقل یک ردیف در فایل بارگذاری شده وارد کنید.', }, + executions: '{{num}} اعدام', + execution: 'اجرا', }, } 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/workflow.ts b/web/i18n/fa-IR/workflow.ts index 71fd48d5c2..6b887b1739 100644 --- a/web/i18n/fa-IR/workflow.ts +++ b/web/i18n/fa-IR/workflow.ts @@ -106,6 +106,15 @@ const translation = { addFailureBranch: 'افزودن برنچ Fail', noHistory: 'بدون تاریخچه', loadMore: 'بارگذاری گردش کار بیشتر', + exportPNG: 'صادرات به فرمت PNG', + noExist: 'هیچگونه متغیری وجود ندارد', + exitVersions: 'نسخه‌های خروجی', + referenceVar: 'متغیر مرجع', + exportSVG: 'صادرات به فرمت SVG', + exportJPEG: 'صادرات به فرمت JPEG', + exportImage: 'تصویر را صادر کنید', + versionHistory: 'تاریخچه نسخه', + publishUpdate: 'به‌روزرسانی منتشر کنید', }, env: { envPanelTitle: 'متغیرهای محیطی', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'تکرار اجرای آزمایشی', back: 'بازگشت', iteration: 'تکرار', + loop: 'حلقه', }, tabs: { 'searchBlock': 'جستجوی بلوک', @@ -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: 'بزرگ‌نمایی', @@ -404,6 +419,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 +459,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 +575,8 @@ const translation = { 'all of': 'همه از', 'not in': 'نه در', 'exists': 'موجود', + 'after': 'بعد از', + 'before': 'قبل از', }, enterValue: 'مقدار را وارد کنید', addCondition: 'افزودن شرط', @@ -520,6 +592,7 @@ const translation = { }, select: 'انتخاب', addSubVariable: 'متغیر فرعی', + condition: 'شرط', }, variableAssigner: { title: 'تخصیص متغیرها', @@ -562,6 +635,8 @@ const translation = { '-=': '-=', 'append': 'الحاق', '/=': '/=', + 'remove-first': 'حذف اول', + 'remove-last': 'آخرین را حذف کنید', }, 'noVarTip': 'برای افزودن متغیرها روی دکمه "+" کلیک کنید', 'selectAssignedVariable': 'متغیر اختصاص داده شده را انتخاب کنید...', @@ -766,6 +841,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 +884,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.ts b/web/i18n/fr-FR/app.ts index 005418108a..16353d9962 100644 --- a/web/i18n/fr-FR/app.ts +++ b/web/i18n/fr-FR/app.ts @@ -159,6 +159,10 @@ 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', @@ -194,6 +198,16 @@ 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', + }, } 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..d26deb3a9f 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', @@ -153,6 +157,9 @@ const translation = { community: 'Communauté', about: 'À propos', logout: 'Se déconnecter', + support: 'Soutien', + github: 'GitHub', + compliance: 'Conformité', }, settings: { accountGroup: 'COMPTE', @@ -202,6 +209,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', @@ -543,6 +553,7 @@ const translation = { inputPlaceholder: 'Parler au bot', 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', @@ -637,6 +648,25 @@ const translation = { 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', + }, } export default translation diff --git a/web/i18n/fr-FR/custom.ts b/web/i18n/fr-FR/custom.ts index c0c651cdb7..d2c0b9d008 100644 --- a/web/i18n/fr-FR/custom.ts +++ b/web/i18n/fr-FR/custom.ts @@ -3,6 +3,8 @@ 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', 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..ba2985ca2b 100644 --- a/web/i18n/fr-FR/dataset.ts +++ b/web/i18n/fr-FR/dataset.ts @@ -168,6 +168,53 @@ 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', + }, + 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/plugin.ts b/web/i18n/fr-FR/plugin.ts index 39fef6e91f..04269e75ed 100644 --- a/web/i18n/fr-FR/plugin.ts +++ b/web/i18n/fr-FR/plugin.ts @@ -180,6 +180,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', @@ -204,6 +206,10 @@ 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}}', } export default translation diff --git a/web/i18n/fr-FR/share-app.ts b/web/i18n/fr-FR/share-app.ts index 44d03b1e35..d0b3a5047e 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/fr-FR/workflow.ts index c345eb32b9..f6ae2a9ee2 100644 --- a/web/i18n/fr-FR/workflow.ts +++ b/web/i18n/fr-FR/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Variables d\'Environnement', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Itération de l\'exécution de test', back: 'Retour', iteration: 'Itération', + loop: 'Boucle', }, tabs: { 'searchBlock': 'Rechercher un bloc', @@ -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', @@ -404,6 +419,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 +459,32 @@ const translation = { url: 'URL segmentée', metadata: 'Autres métadonnées', }, + metadata: { + options: { + disabled: { + subTitle: 'Ne pas activer le filtrage des métadonnées', + }, + 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 +574,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 +591,7 @@ const translation = { }, select: 'Choisir', addSubVariable: 'Sous-variable', + condition: 'Condition', }, variableAssigner: { title: 'Attribuer des variables', @@ -562,6 +634,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', @@ -766,6 +840,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 +883,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.ts b/web/i18n/hi-IN/app.ts index c9b035568b..aef667ec89 100644 --- a/web/i18n/hi-IN/app.ts +++ b/web/i18n/hi-IN/app.ts @@ -159,6 +159,10 @@ const translation = { title: 'ओपिक', description: 'ओपिक एलएलएम अनुप्रयोगों के मूल्यांकन, परीक्षण और निगरानी के लिए एक ओपन-सोर्स प्लेटफॉर्म है।', }, + weave: { + title: 'बुनना', + description: 'वीव एक ओपन-सोर्स प्लेटफ़ॉर्म है जो LLM अनुप्रयोगों का मूल्यांकन, परीक्षण और निगरानी करने के लिए है।', + }, }, answerIcon: { title: 'बदलने 🤖 के लिए WebApp चिह्न का उपयोग करें', @@ -194,6 +198,17 @@ const translation = { placeholder: 'एक ऐप चुनें...', label: 'ऐप', }, + structOutput: { + structured: 'संरचित', + required: 'आवश्यक', + LLMResponse: 'LLM प्रतिक्रिया', + moreFillTip: 'अधिकतम 10 स्तरों की नेस्टिंग दिखाना', + modelNotSupported: 'मॉडल का समर्थन नहीं किया गया', + configure: 'कॉन्फ़िगर करें', + notConfiguredTip: 'संरचित आउटपुट को अभी तक कॉन्फ़िगर नहीं किया गया है', + structuredTip: 'संरचित आउटपुट एक विशेषता है जो यह सुनिश्चित करती है कि मॉडल हमेशा आपके प्रदान किए गए JSON स्कीमा के अनुसार प्रतिक्रियाएँ生成 करेगा।', + modelNotSupportedTip: 'वर्तमान मॉडल इस सुविधा का समर्थन नहीं करता है और स्वचालित रूप से प्रॉम्प्ट इंजेक्शन में डाउनग्रेड किया जाता है।', + }, } 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..4964bcbcc4 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}} आवश्यक है', @@ -162,6 +166,9 @@ const translation = { community: 'समुदाय', about: 'के बारे में', logout: 'लॉग आउट', + compliance: 'अनुपालन', + github: 'गिटहब', + support: 'समर्थन', }, settings: { accountGroup: 'खाता', @@ -212,6 +219,9 @@ const translation = { permanentlyDeleteButton: 'खाता स्थायी रूप से हटाएं', verificationPlaceholder: '6-अंकीय कोड पेस्ट करें', deleteSuccessTip: 'आपके खाते को हटाने का काम पूरा करने के लिए समय चाहिए. जब यह सब हो जाएगा तो हम आपको ईमेल करेंगे।', + workspaceIcon: 'कार्यस्थल आइकन', + editWorkspaceInfo: 'कार्यक्षेत्र की जानकारी संपादित करें', + workspaceName: 'कार्यस्थल का नाम', }, members: { team: 'टीम', @@ -476,7 +486,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 +575,7 @@ const translation = { inputPlaceholder: 'बॉट से बात करें', thought: 'विचार', thinking: 'सोचते हुए...', + resend: 'फिर से भेजें', }, promptEditor: { placeholder: @@ -659,6 +670,25 @@ const translation = { 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: 'अपनी छवि यहाँ छोड़ें, या', + }, } export default translation diff --git a/web/i18n/hi-IN/custom.ts b/web/i18n/hi-IN/custom.ts index ea281d776a..a654fce942 100644 --- a/web/i18n/hi-IN/custom.ts +++ b/web/i18n/hi-IN/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: 'अपग्रेड करें अपने प्लान को', suffix: 'स्वयं अपना ब्रांड चुनना।', + title: 'अपने योजना को अपग्रेड करें', + des: 'अपने ब्रांड को कस्टमाइज़ करने के लिए अपने योजना को अपग्रेड करें', }, webapp: { title: 'WebApp का ब्रांड व्यक्तिकरण करें', 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..c89097d89e 100644 --- a/web/i18n/hi-IN/dataset-settings.ts +++ b/web/i18n/hi-IN/dataset-settings.ts @@ -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/plugin.ts b/web/i18n/hi-IN/plugin.ts index 36b7588319..075e9a1da4 100644 --- a/web/i18n/hi-IN/plugin.ts +++ b/web/i18n/hi-IN/plugin.ts @@ -180,6 +180,8 @@ const translation = { difyMarketplace: 'डिफाई मार्केटप्लेस', sortBy: 'काला शहर', discover: 'खोजें', + partnerTip: 'Dify भागीदार द्वारा सत्यापित', + verifiedTip: 'डिफाई द्वारा सत्यापित', }, task: { clearAll: 'सभी साफ करें', @@ -204,6 +206,10 @@ const translation = { findMoreInMarketplace: 'मार्केटप्लेस में और खोजें', endpointsEnabled: '{{num}} एंडपॉइंट्स के सेट सक्षम किए गए', from: 'से', + metadata: { + title: 'प्लगइन्स', + }, + difyVersionNotCompatible: 'वर्तमान डिफाई संस्करण इस प्लगइन के साथ संगत नहीं है, कृपया आवश्यक न्यूनतम संस्करण में अपग्रेड करें: {{minimalDifyVersion}}', } export default translation diff --git a/web/i18n/hi-IN/share-app.ts b/web/i18n/hi-IN/share-app.ts index 88890f86b8..e0296fda83 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,8 @@ const translation = { 'रॉ {{rowIndex}}: {{varName}} मान {{maxLength}} वर्णों से अधिक नहीं हो सकता', atLeastOne: 'कृपया अपलोड की गई फ़ाइल में कम से कम एक पंक्ति भरें।', }, + execution: 'अनु执行', + executions: '{{num}} फाँसी', }, } 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/workflow.ts b/web/i18n/hi-IN/workflow.ts index c0e679ab8c..17d33a0325 100644 --- a/web/i18n/hi-IN/workflow.ts +++ b/web/i18n/hi-IN/workflow.ts @@ -109,6 +109,15 @@ const translation = { addFailureBranch: 'असफल शाखा जोड़ें', noHistory: 'कोई इतिहास नहीं', loadMore: 'अधिक वर्कफ़्लोज़ लोड करें', + exitVersions: 'निकलने के संस्करण', + exportPNG: 'PNG के रूप में निर्यात करें', + exportJPEG: 'JPEG के रूप में निर्यात करें', + referenceVar: 'संदर्भ चर', + noExist: 'कोई ऐसा चर नहीं है', + exportImage: 'छवि निर्यात करें', + publishUpdate: 'अपडेट प्रकाशित करें', + exportSVG: 'SVG के रूप में निर्यात करें', + versionHistory: 'संस्करण इतिहास', }, env: { envPanelTitle: 'पर्यावरण चर', @@ -208,6 +217,7 @@ const translation = { testRunIteration: 'परीक्षण रन पुनरावृत्ति', back: 'वापस', iteration: 'पुनरावृत्ति', + loop: 'लूप', }, tabs: { 'searchBlock': 'ब्लॉक खोजें', @@ -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: 'ज़ूम इन', @@ -417,6 +432,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 +472,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 +590,8 @@ const translation = { 'not exists': 'मौजूद नहीं है', 'exists': 'मौजूद है', 'not in': 'नहीं है', + 'before': 'पहले', + 'after': 'बाद में', }, enterValue: 'मान दर्ज करें', addCondition: 'शर्त जोड़ें', @@ -535,6 +607,7 @@ const translation = { }, select: 'चुनना', addSubVariable: 'उप चर', + condition: 'स्थिति', }, variableAssigner: { title: 'वेरिएबल्स असाइन करें', @@ -578,6 +651,8 @@ const translation = { 'extend': 'पसार', '-=': '-=', 'append': 'संलग्न', + 'remove-first': 'पहला हटाओ', + 'remove-last': 'अंतिम हटाएँ', }, 'setParameter': 'पैरामीटर सेट करें...', 'noVarTip': 'चर जोड़ने के लिए "+" बटन पर क्लिक करें', @@ -786,6 +861,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 +904,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.ts b/web/i18n/it-IT/app.ts index a33dd5c571..43fe626405 100644 --- a/web/i18n/it-IT/app.ts +++ b/web/i18n/it-IT/app.ts @@ -171,6 +171,10 @@ 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', @@ -206,6 +210,16 @@ 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.', + }, } 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..f337990aab 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', @@ -162,6 +166,9 @@ const translation = { community: 'Comunità', about: 'Informazioni', logout: 'Esci', + support: 'Supporto', + compliance: 'Conformità', + github: 'GitHub', }, settings: { accountGroup: 'ACCOUNT', @@ -214,6 +221,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 +493,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', @@ -574,6 +584,7 @@ const translation = { inputPlaceholder: 'Parla con il bot', thinking: 'Pensante...', thought: 'Pensiero', + resend: 'Reinvia', }, promptEditor: { placeholder: @@ -668,6 +679,25 @@ const translation = { 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', + }, } export default translation diff --git a/web/i18n/it-IT/custom.ts b/web/i18n/it-IT/custom.ts index 7eb2efcf36..b83769654c 100644 --- a/web/i18n/it-IT/custom.ts +++ b/web/i18n/it-IT/custom.ts @@ -3,6 +3,8 @@ 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', 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/plugin.ts b/web/i18n/it-IT/plugin.ts index 2c57e5b7af..2832776b9d 100644 --- a/web/i18n/it-IT/plugin.ts +++ b/web/i18n/it-IT/plugin.ts @@ -180,6 +180,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', @@ -204,6 +206,10 @@ const translation = { 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}}', } export default translation diff --git a/web/i18n/it-IT/share-app.ts b/web/i18n/it-IT/share-app.ts index 772a6e902d..2e1c96a396 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/it-IT/workflow.ts index 58d3455cd0..35d3ba1267 100644 --- a/web/i18n/it-IT/workflow.ts +++ b/web/i18n/it-IT/workflow.ts @@ -110,6 +110,15 @@ 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', }, env: { envPanelTitle: 'Variabili d\'Ambiente', @@ -210,6 +219,7 @@ const translation = { testRunIteration: 'Iterazione Esecuzione Test', back: 'Indietro', iteration: 'Iterazione', + loop: 'Anello', }, tabs: { 'searchBlock': 'Cerca blocco', @@ -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', @@ -421,6 +436,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 +476,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 +593,7 @@ const translation = { 'not in': 'non in', 'exists': 'Esiste', 'not exists': 'non esiste', + 'after': 'dopo', }, enterValue: 'Inserisci valore', addCondition: 'Aggiungi Condizione', @@ -539,6 +609,7 @@ const translation = { }, addSubVariable: 'Variabile secondaria', select: 'Selezionare', + condition: 'Condizione', }, variableAssigner: { title: 'Assegna variabili', @@ -582,6 +653,8 @@ const translation = { 'over-write': 'Sovrascrivere', 'extend': 'Estendere', 'clear': 'Chiaro', + 'remove-last': 'Rimuovi ultimo', + 'remove-first': 'Rimuovi primo', }, 'setParameter': 'Imposta parametro...', 'variables': 'Variabili', @@ -793,6 +866,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 +909,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.ts b/web/i18n/ja-JP/app.ts index ae57a8f801..d68768661e 100644 --- a/web/i18n/ja-JP/app.ts +++ b/web/i18n/ja-JP/app.ts @@ -47,7 +47,7 @@ const translation = { advancedFor: '上級ユーザー向け', advancedDescription: 'ワークフロー オーケストレートは、ワークフロー形式でチャットボットをオーケストレートし、組み込みのプロンプトを編集する機能を含む高度なカスタマイズを提供します。経験豊富なユーザー向けです。', captionName: 'アプリのアイコンと名前', - appNamePlaceholder: 'アプリに名前を付ける', + appNamePlaceholder: 'アプリ名を入力してください', captionDescription: '説明', appDescriptionPlaceholder: 'アプリの説明を入力してください', useTemplate: 'このテンプレートを使用する', @@ -164,6 +164,10 @@ const translation = { title: 'オピック', description: 'Opik は、LLM アプリケーションを評価、テスト、監視するためのオープンソース プラットフォームです。', }, + weave: { + description: 'Weaveは、LLMアプリケーションを評価、テスト、および監視するためのオープンソースプラットフォームです。', + title: '織る', + }, }, answerIcon: { title: 'Webアプリアイコンを使用して🤖を置き換える', diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts index bcb509b85b..1b85449b33 100644 --- a/web/i18n/ja-JP/billing.ts +++ b/web/i18n/ja-JP/billing.ts @@ -175,12 +175,18 @@ const translation = { apps: { fullTipLine1: 'より多くのアプリを作成するには、', fullTipLine2: 'プランをアップグレードしてください。', + fullTip1: 'アプリをもっと作成するためにアップグレードする', + contactUs: 'お問い合わせ', + fullTip2: 'プランの制限に達しました', + fullTip2des: '使用状況を解放するために非アクティブなアプリケーションを整理することをお勧めします。または、お問い合わせください。', + fullTip1des: 'このプランでのアプリ構築の制限に達しました', }, 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 9f480c5af6..e85f8d2228 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: 'アクションが成功しました', @@ -57,6 +63,7 @@ const translation = { copied: 'コピーしました', in: '中', format: 'フォーマット', + more: 'もっと', }, errorMsg: { fieldRequired: '{{field}}は必要です', @@ -162,6 +169,7 @@ const translation = { community: 'コミュニティ', about: 'Difyについて', logout: 'ログアウト', + github: 'ギットハブ', }, compliance: { soc2Type1: 'SOC 2 Type I 報告書', @@ -475,7 +483,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拡張機能', @@ -563,6 +571,7 @@ const translation = { inputPlaceholder: 'ボットと話す', thought: '思考', thinking: '考え中...', + resend: '再送信してください', }, promptEditor: { placeholder: 'ここにプロンプトワードを入力してください。変数を挿入するには「{」を、プロンプトコンテンツブロックを挿入するには「/」を入力します。', @@ -653,6 +662,11 @@ const translation = { pagination: { perPage: 'ページあたりのアイテム数', }, + imageInput: { + browse: 'ブラウズする', + supportedFormats: 'PNG、JPG、JPEG、WEBP、およびGIFをサポートしています。', + dropImageHere: 'ここに画像をドロップするか、', + }, } export default translation diff --git a/web/i18n/ja-JP/dataset-creation.ts b/web/i18n/ja-JP/dataset-creation.ts index 401f0b9084..a3f573cab1 100644 --- a/web/i18n/ja-JP/dataset-creation.ts +++ b/web/i18n/ja-JP/dataset-creation.ts @@ -30,7 +30,7 @@ const translation = { }, uploader: { title: 'テキストファイルをアップロード', - button: 'ファイルをドラッグ&ドロップするか', + button: 'ファイルまたはフォルダをドラッグアンドドロップする', browse: '参照', tip: '{{supportTypes}}をサポートしています。1つあたりの最大サイズは{{size}}MBです。', validation: { @@ -72,7 +72,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', @@ -93,6 +93,12 @@ const translation = { scrapTimeInfo: '{{time}} 秒以内に合計 {{total}} ページをスクレイピングしました', preview: 'プレビュー', 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: { @@ -200,6 +206,11 @@ const translation = { description: '現在、Difyのナレッジベースには利用できるデータソースが限られています。Difyのナレッジベースにデータソースを提供いただくことは、プラットフォームの柔軟性と能力を向上させる上で非常に有益です。貢献ガイドをご用意していますので、ぜひご協力ください。詳細については、以下のリンクをクリックしてください。', learnMore: '詳細はこちら', }, + watercrawl: { + getApiKeyLinkText: 'watercrawl.devからAPIキーを取得してください。', + configWatercrawl: 'ウォータークローラーを設定する', + apiKeyPlaceholder: 'watercrawl.devからのAPIキー', + }, } export default translation diff --git a/web/i18n/ja-JP/plugin.ts b/web/i18n/ja-JP/plugin.ts index 1d2f1a2fb5..6a27048797 100644 --- a/web/i18n/ja-JP/plugin.ts +++ b/web/i18n/ja-JP/plugin.ts @@ -207,6 +207,9 @@ const translation = { searchInMarketplace: 'マーケットプレイスで検索', submitPlugin: 'プラグインを提出する', difyVersionNotCompatible: '現在のDifyバージョンはこのプラグインと互換性がありません。最小バージョンは{{minimalDifyVersion}}です。', + metadata: { + title: 'プラグイン', + }, } export default translation diff --git a/web/i18n/ja-JP/share-app.ts b/web/i18n/ja-JP/share-app.ts index 948fd28bd9..9db8926394 100644 --- a/web/i18n/ja-JP/share-app.ts +++ b/web/i18n/ja-JP/share-app.ts @@ -30,6 +30,8 @@ const translation = { }, tryToSolve: '問題を解決する', temporarySystemIssue: 'システムに一時的な問題が発生しています', + expand: '拡大', + collapse: '縮小', }, generation: { tabs: { diff --git a/web/i18n/ja-JP/time.ts b/web/i18n/ja-JP/time.ts index e2410dd34b..36d4c699bb 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: '四月', + February: '2月', + June: '6月', + January: '1月', + May: '5月', + August: '八月', + October: '十月', + }, + operation: { + now: '今', + cancel: 'キャンセル', + ok: 'はい', + pickDate: '日付を選択', + }, + title: { + pickTime: 'ピックタイム', + }, + defaultPlaceholder: '時間を選んでください...', +} export default translation diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 0be59283a6..066a5581d7 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -75,7 +75,7 @@ const translation = { exportJPEG: 'JPEGで出力', exportSVG: 'SVGで出力', model: 'モデル', - workflowAsTool: 'ワークフローをツールどして公開する', + workflowAsTool: 'ワークフローをツールとして公開する', configureRequired: '設定が必要', configure: '設定', manageInTools: 'ツールページで管理', @@ -147,7 +147,7 @@ const translation = { namePlaceholder: '変数名を入力', type: 'タイプ', value: 'デフォルト値', - valuePlaceholder: 'デフォルト値、設定しない場合は空白にしでください', + valuePlaceholder: 'デフォルト値、設定しない場合は空白にしてください', description: '説明', descriptionPlaceholder: '変数の説明を入力', editInJSON: 'JSONで編集', @@ -581,6 +581,8 @@ const translation = { 'all of': 'すべての', 'exists': '存在します', 'not exists': '存在しません', + 'before': '前に', + 'after': '後', }, enterValue: '値を入力', addCondition: '条件を追加', @@ -639,6 +641,8 @@ const translation = { 'over-write': '上書き', 'extend': '延ばす', '*=': '*=', + 'remove-last': '最後を削除する', + 'remove-first': '最初を削除する', }, 'setParameter': 'パラメータを設定...', 'selectAssignedVariable': '代入変数を選択...', @@ -757,6 +761,9 @@ const translation = { loopNode: 'ループノード', currentLoopCount: '現在のループ回数: {{count}}', totalLoopCount: '総ループ回数: {{count}}', + error_other: '{{count}} エラー', + error_one: '{{count}} エラー', + comma: ',', }, note: { addNote: 'コメントを追加', diff --git a/web/i18n/ko-KR/app.ts b/web/i18n/ko-KR/app.ts index 89cd274647..aac1cb2e62 100644 --- a/web/i18n/ko-KR/app.ts +++ b/web/i18n/ko-KR/app.ts @@ -155,6 +155,10 @@ const translation = { title: '오픽', description: 'Opik은 LLM 애플리케이션을 평가, 테스트 및 모니터링하기 위한 오픈 소스 플랫폼입니다.', }, + weave: { + title: '직조하다', + description: 'Weave는 LLM 애플리케이션을 평가하고 테스트하며 모니터링하기 위한 오픈 소스 플랫폼입니다.', + }, }, answerIcon: { description: 'WebApp 아이콘을 사용하여 공유 응용 프로그램에서 바꿀🤖지 여부', @@ -190,6 +194,17 @@ const translation = { label: '앱', placeholder: '앱 선택...', }, + structOutput: { + required: '필수', + LLMResponse: 'LLM 응답', + modelNotSupported: '모델이 지원되지 않습니다.', + notConfiguredTip: '구성이 아직 설정되지 않았습니다.', + structured: '구조화된', + configure: '설정하다', + moreFillTip: '최대 10단계 중첩을 표시합니다.', + modelNotSupportedTip: '현재 모델은 이 기능을 지원하지 않으며 자동으로 프롬프트 주입으로 다운그레이드됩니다.', + structuredTip: '구조화된 출력은 모델이 제공한 JSON 스키마를 항상 준수하는 응답을 생성하도록 보장하는 기능입니다.', + }, } export default translation diff --git a/web/i18n/ko-KR/billing.ts b/web/i18n/ko-KR/billing.ts index 94d557fd4b..dfb9f6abb2 100644 --- a/web/i18n/ko-KR/billing.ts +++ b/web/i18n/ko-KR/billing.ts @@ -68,6 +68,7 @@ const translation = { messageRequest: { title: '메시지 크레딧', tooltip: 'GPT 제외 다양한 요금제에서의 메시지 호출 쿼터 (gpt4 제외). 제한을 초과하는 메시지는 OpenAI API 키를 사용합니다.', + titlePerMonth: '{{count,number}} 메시지/월', }, annotatedResponse: { title: '주석 응답 쿼터', @@ -77,27 +78,94 @@ const translation = { 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회', 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..4809eb9a2d 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: '입력해주세요', @@ -149,6 +153,9 @@ const translation = { community: '커뮤니티', about: 'Dify 소개', logout: '로그아웃', + github: '깃허브', + compliance: '컴플라이언스', + support: '지원', }, settings: { accountGroup: '계정', @@ -198,6 +205,9 @@ const translation = { feedbackLabel: '계정을 삭제한 이유를 알려주시겠습니까?', feedbackPlaceholder: '선택적', deletePrivacyLinkTip: '당사가 귀하의 데이터를 처리하는 방법에 대한 자세한 내용은 다음을 참조하십시오.', + workspaceIcon: '작업 공간 아이콘', + editWorkspaceInfo: '작업 공간 정보 편집', + workspaceName: '작업 공간 이름', }, members: { team: '팀', @@ -451,7 +461,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 기반 확장', @@ -539,6 +549,7 @@ const translation = { inputPlaceholder: '봇과 대화', thought: '생각', thinking: '생각...', + resend: '재전송', }, promptEditor: { placeholder: '여기에 프롬프트 단어를 입력하세요. 변수를 삽입하려면 "{{"를 입력하고, 프롬프트 컨텐츠 블록을 삽입하려면 "/"를 입력하세요.', @@ -633,6 +644,25 @@ const translation = { 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: '여기에 이미지를 드롭하거나', + }, } export default translation diff --git a/web/i18n/ko-KR/custom.ts b/web/i18n/ko-KR/custom.ts index 6205a67275..8b4954993f 100644 --- a/web/i18n/ko-KR/custom.ts +++ b/web/i18n/ko-KR/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: '플랜을 업그레이드하여', suffix: '브랜드를 사용자 정의하세요.', + des: '계획을 업그레이드하여 브랜드를 맞춤화하세요.', + title: '플랜을 업그레이드하세요', }, webapp: { title: 'WebApp 브랜드 사용자 정의', diff --git a/web/i18n/ko-KR/dataset-creation.ts b/web/i18n/ko-KR/dataset-creation.ts index b40be59fce..4b5ee3f03f 100644 --- a/web/i18n/ko-KR/dataset-creation.ts +++ b/web/i18n/ko-KR/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: '텍스트 파일 업로드', - button: '파일을 끌어다 놓거나', + button: '파일이나 폴더를 끌어서 놓기', browse: '찾아보기', tip: '{{supportTypes}}을(를) 지원합니다. 파일당 최대 크기는 {{size}}MB입니다.', validation: { @@ -52,7 +52,7 @@ const translation = { 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 문서', @@ -82,6 +82,14 @@ const translation = { jinaReaderTitle: '전체 사이트를 Markdown으로 변환', jinaReaderNotConfigured: 'Jina Reader가 구성되지 않았습니다.', useSitemapTooltip: '사이트맵을 따라 사이트를 크롤링합니다. 그렇지 않은 경우 Jina Reader는 페이지 관련성에 따라 반복적으로 크롤링하여 더 적지만 더 높은 품질의 페이지를 생성합니다.', + watercrawlDoc: '워터크롤 문서', + waterCrawlNotConfiguredDescription: 'API 키로 Watercrawl을 구성하여 사용하십시오.', + watercrawlTitle: 'Watercrawl로 웹 콘텐츠 추출하기', + configureFirecrawl: '파이어크롤 구성하기', + watercrawlDocLink: '웹사이트에서 동기화하기', + configureJinaReader: '지나 리더 설정하기', + waterCrawlNotConfigured: 'Watercrawl이 설정되어 있지 않습니다.', + configureWatercrawl: '워터크롤 구성하기', }, cancel: '취소', }, @@ -201,6 +209,11 @@ const translation = { title: '다른 데이터 소스에 연결하시겠습니까?', description: '현재 Dify의 기술 자료에는 제한된 데이터 소스만 있습니다. Dify 기술 자료에 데이터 소스를 제공하는 것은 모든 사용자를 위해 플랫폼의 유연성과 기능을 향상시키는 데 도움이 되는 환상적인 방법입니다. 기여 가이드를 통해 쉽게 시작할 수 있습니다. 자세한 내용은 아래 링크를 클릭하십시오.', }, + watercrawl: { + getApiKeyLinkText: 'watercrawl.dev에서 API 키를 얻으세요.', + configWatercrawl: '워터크롤 구성하기', + apiKeyPlaceholder: 'watercrawl.dev의 API 키', + }, } export default translation diff --git a/web/i18n/ko-KR/dataset-settings.ts b/web/i18n/ko-KR/dataset-settings.ts index c15fff8db6..b008e37ccd 100644 --- a/web/i18n/ko-KR/dataset-settings.ts +++ b/web/i18n/ko-KR/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: '자세히 알아보기', description: ' 검색 방법에 대한 자세한 정보', longDescription: ' 검색 방법에 대한 자세한 내용은 언제든지 지식 설정에서 변경할 수 있습니다.', + method: '검색 방법', }, save: '저장', permissionsInvitedMembers: '부분 팀 구성원', diff --git a/web/i18n/ko-KR/dataset.ts b/web/i18n/ko-KR/dataset.ts index 4d622cf7f2..6be4dcc8b6 100644 --- a/web/i18n/ko-KR/dataset.ts +++ b/web/i18n/ko-KR/dataset.ts @@ -167,6 +167,54 @@ const translation = { 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..78e4be7052 --- /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..5ae1a34643 100644 --- a/web/i18n/ko-KR/explore.ts +++ b/web/i18n/ko-KR/explore.ts @@ -37,6 +37,7 @@ const translation = { Agent: '에이전트', Workflow: '워크플로우', HR: '인사', + Entertainment: '오락', }, } diff --git a/web/i18n/ko-KR/plugin.ts b/web/i18n/ko-KR/plugin.ts index 06445f3fb7..923f9d5248 100644 --- a/web/i18n/ko-KR/plugin.ts +++ b/web/i18n/ko-KR/plugin.ts @@ -180,6 +180,8 @@ const translation = { moreFrom: 'Marketplace에서 더 보기', sortBy: '정렬', and: '그리고', + verifiedTip: 'Dify에 의해 확인됨', + partnerTip: 'Dify 파트너에 의해 확인됨', }, task: { installingWithSuccess: '{{installingLength}} 플러그인 설치, {{successLength}} 성공.', @@ -204,6 +206,10 @@ const translation = { searchPlugins: '검색 플러그인', install: '{{num}} 설치', fromMarketplace: 'Marketplace에서', + metadata: { + title: '플러그인', + }, + difyVersionNotCompatible: '현재 Dify 버전이 이 플러그인과 호환되지 않습니다. 필요한 최소 버전으로 업그레이드하십시오: {{minimalDifyVersion}}', } export default translation diff --git a/web/i18n/ko-KR/share-app.ts b/web/i18n/ko-KR/share-app.ts index be2e34a5fc..8474bc554e 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: { @@ -64,6 +70,8 @@ const translation = { moreThanMaxLengthLine: '줄 {{rowIndex}}: {{varName}}의 값은 {{maxLength}}자를 초과할 수 없습니다.', atLeastOne: '업로드된 파일에는 적어도 한 줄의 입력이 필요합니다.', }, + execution: '실행', + executions: '{{num}} 처형', }, } diff --git a/web/i18n/ko-KR/time.ts b/web/i18n/ko-KR/time.ts index e2410dd34b..78e825d1ba 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/workflow.ts b/web/i18n/ko-KR/workflow.ts index 17297f1273..a3496b7a23 100644 --- a/web/i18n/ko-KR/workflow.ts +++ b/web/i18n/ko-KR/workflow.ts @@ -106,6 +106,15 @@ const translation = { addFailureBranch: '실패 분기 추가', noHistory: '이력 없음', loadMore: '더 많은 워크플로우 로드', + publishUpdate: '업데이트 게시', + exportJPEG: 'JPEG로 내보내기', + exitVersions: '종료 버전', + exportImage: '이미지 내보내기', + noExist: '해당 변수가 없습니다.', + exportSVG: 'SVG로 내보내기', + versionHistory: '버전 기록', + exportPNG: 'PNG로 내보내기', + referenceVar: '참조 변수', }, env: { envPanelTitle: '환경 변수', @@ -205,6 +214,7 @@ const translation = { testRunIteration: '테스트 실행 반복', back: '뒤로', iteration: '반복', + loop: '루프', }, tabs: { 'searchBlock': '블록 검색', @@ -243,6 +253,9 @@ const translation = { 'document-extractor': 'Doc 추출기', 'list-operator': 'List 연산자', 'agent': '대리인', + 'loop-start': '루프 시작', + 'loop-end': '루프 종료', + 'loop': '루프', }, blocksAbout: { 'start': '워크플로우를 시작하기 위한 초기 매개변수를 정의합니다', @@ -263,6 +276,8 @@ const translation = { 'document-extractor': '업로드된 문서를 LLM에서 쉽게 이해할 수 있는 텍스트 콘텐츠로 구문 분석하는 데 사용됩니다.', 'list-operator': '배열 내용을 필터링하거나 정렬하는 데 사용됩니다.', 'agent': '질문에 답하거나 자연어를 처리하기 위해 대규모 언어 모델을 호출하는 경우', + 'loop': '종료 조건이 충족되거나 최대 반복 횟수에 도달할 때까지 논리 루프를 실행합니다.', + 'loop-end': '"break"와 동일합니다. 이 노드는 구성 항목이 없습니다. 루프 본문이 이 노드에 도달하면 루프가 종료됩니다.', }, operator: { zoomIn: '확대', @@ -404,6 +419,34 @@ const translation = { variable: '변수', }, 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,6 +459,33 @@ 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: '입력 변수', @@ -505,6 +575,8 @@ const translation = { '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': '사용 가능한 할당된 변수가 없습니다.', @@ -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..87027a7951 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 = { + '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 = { + 'ja-JP': 'jp', +} + +export const getPricingPageLanguage = (locale: string) => { + return PRICING_PAGE_LANGUAGE[locale] || '' +} + export const NOTICE_I18N = { title: { en_US: 'Important Notice', diff --git a/web/i18n/pl-PL/app.ts b/web/i18n/pl-PL/app.ts index 562962bf38..137fdebeae 100644 --- a/web/i18n/pl-PL/app.ts +++ b/web/i18n/pl-PL/app.ts @@ -166,6 +166,10 @@ 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 🤖.', @@ -201,6 +205,17 @@ 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.', + }, } 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..fb94c8caaf 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ć', @@ -158,6 +162,9 @@ const translation = { community: 'Społeczność', about: 'O', logout: 'Wyloguj się', + support: 'Wsparcie', + github: 'GitHub', + compliance: 'Zgodność', }, settings: { accountGroup: 'KONTO', @@ -208,6 +215,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 +479,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', @@ -558,6 +568,7 @@ const translation = { inputPlaceholder: 'Porozmawiaj z botem', thought: 'Myśl', thinking: 'Myślenie...', + resend: 'Prześlij ponownie', }, promptEditor: { placeholder: @@ -655,6 +666,25 @@ const translation = { 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', + }, } 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/plugin.ts b/web/i18n/pl-PL/plugin.ts index e04068e59d..0883a98e07 100644 --- a/web/i18n/pl-PL/plugin.ts +++ b/web/i18n/pl-PL/plugin.ts @@ -180,6 +180,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ć', @@ -204,6 +206,10 @@ const translation = { 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}}', } export default translation diff --git a/web/i18n/pl-PL/share-app.ts b/web/i18n/pl-PL/share-app.ts index 90b6ca1929..80619cf4fc 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,8 @@ const translation = { atLeastOne: 'Proszę wprowadź co najmniej jeden wiersz w załadowanym pliku.', }, + executions: '{{num}} EGZEKUCJI', + execution: 'WYKONANIE', }, } 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/workflow.ts b/web/i18n/pl-PL/workflow.ts index c47f4ea7d0..ebf1f47d2b 100644 --- a/web/i18n/pl-PL/workflow.ts +++ b/web/i18n/pl-PL/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Zmienne Środowiskowe', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Iteracja testowego uruchomienia', back: 'Wstecz', iteration: 'Iteracja', + loop: 'Pętla', }, tabs: { 'searchBlock': 'Szukaj bloku', @@ -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', @@ -404,6 +419,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 +459,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 +575,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 +592,7 @@ const translation = { }, addSubVariable: 'Zmienna podrzędna', select: 'Wybrać', + condition: 'Stan', }, variableAssigner: { title: 'Przypisz zmienne', @@ -562,6 +635,8 @@ const translation = { '+=': '+=', 'clear': 'Jasny', 'append': 'Dołączyć', + 'remove-first': 'Usuń pierwszy', + 'remove-last': 'Usuń ostatni', }, 'variables': 'Zmiennych', 'selectAssignedVariable': 'Wybierz przypisaną zmienną...', @@ -766,6 +841,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 +884,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.ts b/web/i18n/pt-BR/app.ts index 8f920e4280..c09c2dea7b 100644 --- a/web/i18n/pt-BR/app.ts +++ b/web/i18n/pt-BR/app.ts @@ -159,6 +159,10 @@ 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', @@ -194,6 +198,17 @@ 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.', + }, } 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..7b7eeafbe7 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', @@ -153,6 +157,9 @@ const translation = { community: 'Comunidade', about: 'Sobre', logout: 'Sair', + github: 'GitHub', + support: 'Suporte', + compliance: 'Conformidade', }, settings: { accountGroup: 'CONTA', @@ -202,6 +209,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 +465,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', @@ -543,6 +553,7 @@ const translation = { inputPlaceholder: 'Fale com o bot', 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', @@ -637,6 +648,25 @@ const translation = { 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', + }, } export default translation diff --git a/web/i18n/pt-BR/custom.ts b/web/i18n/pt-BR/custom.ts index 940316e7bb..c1c7251f48 100644 --- a/web/i18n/pt-BR/custom.ts +++ b/web/i18n/pt-BR/custom.ts @@ -3,6 +3,8 @@ 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', 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/plugin.ts b/web/i18n/pt-BR/plugin.ts index 3528407a1b..c02f9cb7e3 100644 --- a/web/i18n/pt-BR/plugin.ts +++ b/web/i18n/pt-BR/plugin.ts @@ -180,6 +180,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}}', @@ -204,6 +206,10 @@ 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}}', } export default translation diff --git a/web/i18n/pt-BR/share-app.ts b/web/i18n/pt-BR/share-app.ts index 1e1861e01b..d8bca03089 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/pt-BR/workflow.ts index 3d2b3fe8f0..e64240a06d 100644 --- a/web/i18n/pt-BR/workflow.ts +++ b/web/i18n/pt-BR/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Variáveis de Ambiente', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Iteração de execução de teste', back: 'Voltar', iteration: 'Iteração', + loop: 'Laço', }, tabs: { 'searchBlock': 'Buscar bloco', @@ -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', @@ -404,6 +419,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 +459,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 +575,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 +592,7 @@ const translation = { }, addSubVariable: 'Subvariável', select: 'Selecionar', + condition: 'Condição', }, variableAssigner: { title: 'Atribuir variáveis', @@ -562,6 +635,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...', @@ -766,6 +841,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 +884,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.ts b/web/i18n/ro-RO/app.ts index 3f288c1396..1b1cd6c25d 100644 --- a/web/i18n/ro-RO/app.ts +++ b/web/i18n/ro-RO/app.ts @@ -159,6 +159,10 @@ 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', @@ -194,6 +198,17 @@ 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.', + }, } 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..13243404bd 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', @@ -153,6 +157,9 @@ const translation = { community: 'Comunitate', about: 'Despre', logout: 'Deconectare', + github: 'GitHub', + support: 'Suport', + compliance: 'Conformitate', }, settings: { accountGroup: 'CONT', @@ -202,6 +209,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 +465,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', @@ -543,6 +553,7 @@ const translation = { inputPlaceholder: 'Vorbește cu Bot', 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', @@ -637,6 +648,25 @@ const translation = { 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', + }, } export default translation diff --git a/web/i18n/ro-RO/custom.ts b/web/i18n/ro-RO/custom.ts index 0e10d59ec0..923ec39759 100644 --- a/web/i18n/ro-RO/custom.ts +++ b/web/i18n/ro-RO/custom.ts @@ -3,6 +3,8 @@ 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', 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/plugin.ts b/web/i18n/ro-RO/plugin.ts index db21cbc40a..4fe0782496 100644 --- a/web/i18n/ro-RO/plugin.ts +++ b/web/i18n/ro-RO/plugin.ts @@ -180,6 +180,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', @@ -204,6 +206,10 @@ 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}}', } export default translation diff --git a/web/i18n/ro-RO/share-app.ts b/web/i18n/ro-RO/share-app.ts index c9ec36ab03..2cb39a0485 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/ro-RO/workflow.ts index b4aa035274..2978565bfc 100644 --- a/web/i18n/ro-RO/workflow.ts +++ b/web/i18n/ro-RO/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Variabile de Mediu', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Iterație rulare de test', back: 'Înapoi', iteration: 'Iterație', + loop: 'Loop', }, tabs: { 'searchBlock': 'Caută bloc', @@ -243,6 +253,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 +276,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', @@ -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ă', @@ -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.ts b/web/i18n/ru-RU/app.ts index a5b543c867..990457b950 100644 --- a/web/i18n/ru-RU/app.ts +++ b/web/i18n/ru-RU/app.ts @@ -163,6 +163,10 @@ const translation = { title: 'Опик', description: 'Opik — это платформа с открытым исходным кодом для оценки, тестирования и мониторинга LLM-приложений.', }, + weave: { + description: 'Weave — это открытая платформа для оценки, тестирования и мониторинга приложений LLM.', + title: 'Ткать', + }, }, answerIcon: { title: 'Использование значка WebApp для замены 🤖', @@ -194,6 +198,17 @@ const translation = { placeholder: 'Выберите приложение...', params: 'ПАРАМЕТРЫ ПРИЛОЖЕНИЯ', }, + structOutput: { + notConfiguredTip: 'Структурированный вывод еще не был настроен.', + LLMResponse: 'Ответ LLM', + structured: 'Структурированный', + moreFillTip: 'Показано максимум 10 уровней вложенности', + required: 'Необходимо', + configure: 'Настроить', + modelNotSupported: 'Модель не поддерживается', + modelNotSupportedTip: 'Текущая модель не поддерживает эту функцию и автоматически понижается до инъекции подсказок.', + structuredTip: 'Структурированные выходные данные — это функция, которая гарантирует, что модель всегда будет генерировать ответы, соответствующие вашей предоставленной JSON-схеме.', + }, } 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..1c7b41169b 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}} обязательно', @@ -157,6 +161,9 @@ const translation = { community: 'Сообщество', about: 'О нас', logout: 'Выйти', + github: 'ГитХаб', + compliance: 'Соблюдение', + support: 'Поддержка', }, settings: { accountGroup: 'АККАУНТ', @@ -206,6 +213,9 @@ const translation = { deleteLabel: 'Для подтверждения, пожалуйста, введите свой адрес электронной почты ниже', deleteSuccessTip: 'Вашему аккаунту требуется время, чтобы завершить удаление. Мы свяжемся с вами по электронной почте, когда все будет готово.', deletePrivacyLinkTip: 'Для получения дополнительной информации о том, как мы обрабатываем ваши данные, ознакомьтесь с нашим', + workspaceIcon: 'Иконка рабочего пространства', + workspaceName: 'Название рабочего пространства', + editWorkspaceInfo: 'Редактировать информацию о рабочем пространстве', }, members: { team: 'Команда', @@ -459,7 +469,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 +557,7 @@ const translation = { inputPlaceholder: 'Поговорить с ботом', thinking: 'Мыслящий...', thought: 'Мысль', + resend: 'Переслать', }, promptEditor: { placeholder: 'Напишите здесь свое ключевое слово подсказки, введите \'{\', чтобы вставить переменную, введите \'/\', чтобы вставить блок содержимого подсказки', @@ -637,6 +648,25 @@ const translation = { 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', + }, } 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/plugin.ts b/web/i18n/ru-RU/plugin.ts index 4fc042f5ec..9d99bc11d9 100644 --- a/web/i18n/ru-RU/plugin.ts +++ b/web/i18n/ru-RU/plugin.ts @@ -180,6 +180,8 @@ const translation = { viewMore: 'Подробнее', and: 'и', discover: 'Обнаруживать', + verifiedTip: 'Подтверждено Dify', + partnerTip: 'Подтверждено партнером Dify', }, task: { installing: 'Установка плагинов {{installingLength}}, 0 готово.', @@ -204,6 +206,10 @@ const translation = { installPlugin: 'Установка плагина', searchPlugins: 'Плагины поиска', fromMarketplace: 'Из маркетплейса', + metadata: { + title: 'Плагины', + }, + difyVersionNotCompatible: 'Текущая версия Dify не совместима с этим плагином, пожалуйста, обновите до минимально необходимой версии: {{minimalDifyVersion}}', } export default translation diff --git a/web/i18n/ru-RU/share-app.ts b/web/i18n/ru-RU/share-app.ts index f0166b26f1..b2850fa276 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,8 @@ const translation = { moreThanMaxLengthLine: 'Строка {{rowIndex}}: значение {{varName}} не может превышать {{maxLength}} символов', atLeastOne: 'Пожалуйста, введите хотя бы одну строку в загруженный файл.', }, + execution: 'ИСПОЛНЕНИЕ', + executions: '{{num}} ВЫПОЛНЕНИЯ', }, } 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/workflow.ts b/web/i18n/ru-RU/workflow.ts index d22ae7bf4f..8a292ff0a9 100644 --- a/web/i18n/ru-RU/workflow.ts +++ b/web/i18n/ru-RU/workflow.ts @@ -106,6 +106,15 @@ const translation = { addFailureBranch: 'Добавить ветвь Fail', noHistory: 'Без истории', loadMore: 'Загрузите больше рабочих процессов', + noExist: 'Такой переменной не существует', + versionHistory: 'История версий', + exportPNG: 'Экспортировать как PNG', + exportImage: 'Экспортировать изображение', + exportJPEG: 'Экспортировать как JPEG', + referenceVar: 'Ссылочная переменная', + exitVersions: 'Выходные версии', + exportSVG: 'Экспортировать как SVG', + publishUpdate: 'Опубликовать обновление', }, env: { envPanelTitle: 'Переменные среды', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Итерация тестового запуска', back: 'Назад', iteration: 'Итерация', + loop: 'Цикл', }, tabs: { 'searchBlock': 'Поиск блока', @@ -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: 'Увеличить', @@ -404,6 +419,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 +459,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 +575,8 @@ const translation = { 'not exists': 'не существует', 'in': 'в', 'exists': 'Существует', + 'before': 'до', + 'after': 'после', }, enterValue: 'Введите значение', addCondition: 'Добавить условие', @@ -520,6 +592,7 @@ const translation = { }, select: 'Выбирать', addSubVariable: 'Подпеременная', + condition: 'Условие', }, variableAssigner: { title: 'Назначить переменные', @@ -562,6 +635,8 @@ const translation = { 'title': 'Операция', 'over-write': 'Перезаписать', 'append': 'Прибавлять', + 'remove-first': 'Удалить первый', + 'remove-last': 'Удалить последний', }, 'variables': 'Переменные', 'noAssignedVars': 'Нет доступных назначенных переменных', @@ -766,6 +841,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 +884,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.ts b/web/i18n/sl-SI/app.ts index e4ba29094b..bff7f2aea1 100644 --- a/web/i18n/sl-SI/app.ts +++ b/web/i18n/sl-SI/app.ts @@ -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,17 @@ 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.', + }, } 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..d4b78d41e5 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', @@ -157,6 +161,9 @@ const translation = { community: 'Skupnost', about: 'O nas', logout: 'Odjava', + support: 'Podpora', + github: 'GitHub', + compliance: 'Skladnost', }, settings: { accountGroup: 'SPLOŠNO', @@ -206,6 +213,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 +462,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 +691,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 +756,7 @@ const translation = { conversationNamePlaceholder: 'Prosimo, vnesite ime pogovora', thinking: 'Razmišljanje...', thought: 'Misel', + resend: 'Ponovno pošlji', }, promptEditor: { context: { @@ -836,6 +847,25 @@ const translation = { 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', + }, } 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/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..848ef39170 100644 --- a/web/i18n/sl-SI/plugin.ts +++ b/web/i18n/sl-SI/plugin.ts @@ -0,0 +1,215 @@ +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.', + }, + 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 zaupanja vrednega vira.', + 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', + submitPlugin: 'Oddajte vtičnik', + difyVersionNotCompatible: 'Trenutna različica Dify ni združljiva s to vtičnico, prosimo, posodobite na minimalno zahtevano različico: {{minimalDifyVersion}}', +} + +export default translation diff --git a/web/i18n/sl-SI/share-app.ts b/web/i18n/sl-SI/share-app.ts index 68ad6594fc..28d62b2336 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/sl-SI/workflow.ts index e4a71ddac3..1376bc833f 100644 --- a/web/i18n/sl-SI/workflow.ts +++ b/web/i18n/sl-SI/workflow.ts @@ -106,6 +106,15 @@ const translation = { onFailure: 'O neuspehu', noHistory: 'Brez zgodovine', loadMore: 'Nalaganje več potekov dela', + exportJPEG: 'Izvozi kot JPEG', + exportPNG: 'Izvozi kot PNG', + noExist: 'Nobena takšna spremenljivka ne obstaja.', + exitVersions: 'Izhodne različice', + versionHistory: 'Zgodovina različic', + publishUpdate: 'Objavi posodobitev', + exportSVG: 'Izvozi kot SVG', + referenceVar: 'Referenčna spremenljivka', + exportImage: 'Izvozi sliko', }, env: { envPanelTitle: 'Spremenljivke okolja', @@ -642,6 +651,7 @@ const translation = { iteration: 'Ponovitev', back: 'Hrbet', testRun: 'Preskusni zagon', + loop: 'Zanka', }, tabs: { 'blocks': 'Bloki', @@ -680,6 +690,9 @@ const translation = { 'variable-assigner': 'Spremenljivi agregator', 'question-classifier': 'Klasifikator vprašanj', 'agent': 'Agent', + 'loop-end': 'Izhod iz zanke', + 'loop': 'Zanka', + 'loop-start': 'Začetek zanke', }, blocksAbout: { 'document-extractor': 'Uporablja se za razčlenjevanje naloženih dokumentov v besedilno vsebino, ki je zlahka razumljiva LLM.', @@ -700,6 +713,8 @@ const translation = { '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', + 'loop': 'Izvedite zanko logike, dokler ni izpolnjen pogoj za prekinitev ali dokler ni dosežena največja število ponovitev.', + 'loop-end': 'Enakovredno „prekini“. Ta vozlišče nima konfiguracijskih elementov. Ko telo zanke doseže to vozlišče, zanka preneha.', }, operator: { zoomOut: 'Pomanjšanje', @@ -841,6 +856,34 @@ const translation = { prompt: 'Uren', vision: 'vid', contextTooltip: 'Znanje lahko uvozite kot kontekst', + jsonSchema: { + warningTips: { + saveSchema: 'Prosimo, da dokončate urejanje trenutnega polja, preden shranite shemo.', + }, + addChildField: 'Dodaj polje za otroka', + instruction: 'Navodilo', + regenerate: 'Ponovno generiranje', + back: 'Nazaj', + generationTip: 'Lahko uporabite naravni jezik za hitro ustvarjanje JSON sheme.', + title: 'Strukturirana izhodna shema', + generating: 'Generiranje JSON sheme...', + showAdvancedOptions: 'Prikaži napredne možnosti', + promptTooltip: 'Pretvorite besedilni opis v standardizirano strukturo JSON sheme.', + generateJsonSchema: 'Generiraj JSON shemo', + fieldNamePlaceholder: 'Ime polja', + apply: 'Prijavi se', + doc: 'Izvedite več o strukturiranem izhodu', + promptPlaceholder: 'Opiši svoj JSON shemo...', + generatedResult: 'Generiran rezultat', + import: 'Uvoz iz JSON', + generate: 'Generirati', + resultTip: 'Tukaj je generiran rezultat. Če niste zadovoljni, se lahko vrnete in spremenite svoj poziv.', + stringValidations: 'Preverjanje nizov', + descriptionPlaceholder: 'Dodajte opis', + required: 'zahtevano', + addField: 'Dodaj polje', + resetDefaults: 'Ponastavi', + }, }, knowledgeRetrieval: { outputVars: { @@ -853,6 +896,33 @@ const translation = { }, queryVariable: 'Spremenljivka poizvedbe', knowledge: 'Znanje', + metadata: { + options: { + disabled: { + title: 'Onemogočeno', + subTitle: 'Ne omogočanje filtriranja metapodatkov', + }, + automatic: { + desc: 'Samodejno ustvarite filtrirne pogoje za metapodatke na podlagi spremenljivke poizvedbe', + subTitle: 'Samodejno ustvarite filtrirne pogoje za metapodatke na podlagi uporabniškega poizvedovanja.', + title: 'Samodejno', + }, + manual: { + title: 'Ročno', + subTitle: 'Ročno dodajte pogoje za filtriranje metapodatkov', + }, + }, + panel: { + title: 'Pogoji za filtriranje metapodatkov', + search: 'Išči metapodatke', + placeholder: 'Vnesite vrednost', + select: 'Izberi spremenljivko...', + conditions: 'Pogoji', + datePlaceholder: 'Izberi čas...', + add: 'Dodaj pogoj', + }, + title: 'Filtriranje metapodatkov', + }, }, http: { outputVars: { @@ -934,6 +1004,8 @@ const translation = { 'end with': 'Končaj z', 'not in': 'ni v', 'not null': 'ni nična', + 'after': 'po', + 'before': 'pred', }, optionName: { video: 'Video', @@ -956,6 +1028,7 @@ const translation = { notSetVariable: 'Prosimo, najprej nastavite spremenljivko', operator: 'Operaterja', or: 'ali', + condition: 'Pogoji', }, variableAssigner: { type: { @@ -998,6 +1071,8 @@ const translation = { 'set': 'Nastaviti', 'clear': 'Jasen', 'over-write': 'Prepisati', + 'remove-last': 'Odstrani zadnje', + 'remove-first': 'Odstrani prvi', }, 'variables': 'Spremenljivke', 'selectAssignedVariable': 'Izberite dodeljeno spremenljivko ...', @@ -1202,6 +1277,38 @@ const translation = { pluginNotInstalledDesc: 'Ta vtičnik je nameščen iz GitHuba. Prosimo, pojdite na Vtičniki za ponovno namestitev', linkToPlugin: 'Povezava do vtičnikov', }, + loop: { + ErrorMethod: { + operationTerminated: 'Prekinjeno', + continueOnError: 'Nadaljuj ob napaki', + removeAbnormalOutput: 'Odstrani nenavadne izhode', + }, + input: 'Vnos', + inputMode: 'Vnosni način', + errorResponseMethod: 'Metoda odziva napake', + setLoopVariables: 'Nastavite spremenljivke znotraj obsega zanke', + output: 'Izhodna spremenljivka', + loop_one: '{{count}} Zanka', + exitConditionTip: 'Vozić potrebuje vsaj eno izhodno pogoj.', + loopMaxCount: 'Maksimalno število zank', + deleteDesc: 'Izbris vozlišča zanke bo odstranil vse otroške vozlišča.', + comma: ',', + loop_other: '{{count}} Zavoji', + currentLoop: 'Trenutni obrat', + variableName: 'Spremenljivka Ime', + deleteTitle: 'Izbriši vozlišče zanke?', + error_one: '{{count}} Napaka', + totalLoopCount: 'Skupno število zank: {{count}}', + initialLoopVariables: 'Začetne spremenljivke zanke', + currentLoopCount: 'Trenutno število zank: {{count}}', + loopNode: 'Ciklični vozlišče', + loopVariables: 'Zanke Spremenljivke', + breakConditionTip: 'Lahko se sklicujete le na spremenljivke znotraj zank z zaključnimi pogoji in pogovorne spremenljivke.', + breakCondition: 'Pogoji za prekinitev zanke', + finalLoopVariables: 'Končne zanke spremenljivke', + error_other: '{{count}} Napak', + loopMaxCountError: 'Prosimo, vnesite veljavno največje število ponovitev, ki mora biti med 1 in {{maxCount}}', + }, }, variableReference: { noVarsForOperation: 'Spremenljivk ni na voljo za dodelitev z izbrano operacijo.', @@ -1210,6 +1317,38 @@ const translation = { noAvailableVars: 'Ni spremenljivk, ki so na voljo', assignedVarsDescription: 'Dodeljene spremenljivke morajo biti zapisljive, kot so:', }, + versionHistory: { + filter: { + all: 'Vse', + empty: 'Ni najdene zgodovine različic, ki bi se ujemala.', + onlyShowNamedVersions: 'Prikaži samo poimenovane različice', + reset: 'Ponastavi filter', + onlyYours: 'Samo tvoje', + }, + editField: { + titleLengthLimit: 'Naslov ne sme presegati {{limit}} znakov', + title: 'Naslov', + releaseNotesLengthLimit: 'Opombe o različici ne smejo presegati {{limit}} znakov.', + releaseNotes: 'Opombe o izdaji', + }, + action: { + updateFailure: 'Posodobitev različice ni uspela', + restoreFailure: 'Obnovitev različice ni uspela', + updateSuccess: 'Različica posodobljena', + restoreSuccess: 'Obnovljena različica', + deleteSuccess: 'Različica izbrisana', + deleteFailure: 'Brisanje različice ni uspelo', + }, + releaseNotesPlaceholder: 'Opisujte, kaj se je spremenilo.', + latest: 'Najnovejši', + deletionTip: 'Izbris je nepovraten, prosim potrdite.', + defaultName: 'Nepodpisana različica', + nameThisVersion: 'Poimenujte to različico', + restorationTip: 'Po obnovitvi različice bo trenutni osnutek prepisan.', + currentDraft: 'Trenutni osnutek', + editVersionInfo: 'Uredi informacije o različici', + title: 'Različice', + }, } export default translation 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.ts b/web/i18n/th-TH/app.ts index 061e3a8076..b58812bf9d 100644 --- a/web/i18n/th-TH/app.ts +++ b/web/i18n/th-TH/app.ts @@ -164,6 +164,10 @@ const translation = { title: 'โอปิก', description: 'Opik เป็นแพลตฟอร์มโอเพ่นซอร์สสําหรับการประเมิน ทดสอบ และตรวจสอบแอปพลิเคชัน LLM', }, + weave: { + title: 'ทอ', + description: 'Weave เป็นแพลตฟอร์มโอเพนซอร์สสำหรับการประเมินผล ทดสอบ และตรวจสอบแอปพลิเคชัน LLM', + }, }, mermaid: { handDrawn: 'วาดด้วยมือ', @@ -190,6 +194,17 @@ const translation = { noParams: 'ไม่จําเป็นต้องใช้พารามิเตอร์', label: 'แอพ', }, + structOutput: { + notConfiguredTip: 'ยังไม่ได้กำหนดผลลัพธ์ที่มีโครงสร้าง', + moreFillTip: 'แสดงระดับการซ้อนสูงสุด 10 ระดับ', + structuredTip: 'Structured Outputs เป็นฟีเจอร์ที่ทำให้มั่นใจว่าโมเดลจะสร้างคำตอบที่สอดคล้องกับ JSON Schema ที่คุณกำหนดไว้เสมอ', + configure: 'กำหนดค่า', + required: 'ที่จำเป็น', + LLMResponse: 'LLM ตอบสนอง', + structured: 'มีระเบียบ', + modelNotSupported: 'โมเดลไม่ได้รับการสนับสนุน', + modelNotSupportedTip: 'โมเดลปัจจุบันไม่รองรับฟีเจอร์นี้และจะถูกลดระดับเป็นการฉีดคำสั่งโดยอัตโนมัติ.', + }, } 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..9b72257a1f 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}} เป็นสิ่งจําเป็น', @@ -152,6 +156,9 @@ const translation = { community: 'ชุมชน', about: 'ประมาณ', logout: 'ออกจากระบบ', + github: 'GitHub', + compliance: 'การปฏิบัติตามข้อกำหนด', + support: 'การสนับสนุน', }, settings: { accountGroup: 'ทั่วไป', @@ -201,6 +208,9 @@ const translation = { feedbackLabel: 'บอกเราว่าทําไมคุณถึงลบบัญชีของคุณ', feedbackPlaceholder: 'เสริม', deleteSuccessTip: 'บัญชีของคุณต้องใช้เวลาในการลบให้เสร็จสิ้น เราจะส่งอีเมลถึงคุณเมื่อทุกอย่างเสร็จสิ้น', + workspaceIcon: 'ไอคอนพื้นที่ทำงาน', + editWorkspaceInfo: 'แก้ไขข้อมูลเวิร์กสเปซ', + workspaceName: 'ชื่อพื้นที่ทำงาน', }, members: { team: 'ทีม', @@ -454,7 +464,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 +552,7 @@ const translation = { inputPlaceholder: 'พูดคุยกับบอท', thought: 'ความคิด', thinking: 'ความคิด ', + resend: 'ส่งอีกครั้ง', }, promptEditor: { placeholder: 'เขียนคําพร้อมท์ของคุณที่นี่ ป้อน \'{\' เพื่อแทรกตัวแปร ป้อน \'/\' เพื่อแทรกบล็อกเนื้อหาพร้อมท์', @@ -632,6 +643,25 @@ const translation = { 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', + }, } export default translation diff --git a/web/i18n/th-TH/custom.ts b/web/i18n/th-TH/custom.ts index dfed65c9a0..c5ae3e79db 100644 --- a/web/i18n/th-TH/custom.ts +++ b/web/i18n/th-TH/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: 'อัปเกรดแผนของคุณเป็น', suffix: 'ปรับแต่งแบรนด์ของคุณ', + des: 'อัปเกรดแผนของคุณเพื่อปรับแต่งแบรนด์ของคุณ', + title: 'อัปเกรดแผนของคุณ', }, webapp: { title: 'ปรับแต่งแบรนด์ WebApp', 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-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/plugin.ts b/web/i18n/th-TH/plugin.ts index 962df3b912..eb42371fbe 100644 --- a/web/i18n/th-TH/plugin.ts +++ b/web/i18n/th-TH/plugin.ts @@ -180,6 +180,8 @@ const translation = { noPluginFound: 'ไม่พบปลั๊กอิน', empower: 'เพิ่มศักยภาพในการพัฒนา AI ของคุณ', difyMarketplace: 'ตลาด Dify', + partnerTip: 'ได้รับการตรวจสอบโดยพันธมิตรของ Dify', + verifiedTip: 'ได้รับการตรวจสอบโดย Dify', }, task: { installing: 'การติดตั้งปลั๊กอิน {{installingLength}} 0 เสร็จแล้ว', @@ -204,6 +206,10 @@ const translation = { fromMarketplace: 'จาก Marketplace', submitPlugin: 'ส่งปลั๊กอิน', allCategories: 'หมวดหมู่ทั้งหมด', + metadata: { + title: 'ปลั๊กอิน', + }, + difyVersionNotCompatible: 'เวอร์ชั่นปัจจุบันของ Dify ไม่สามารถใช้งานร่วมกับปลั๊กอินนี้ได้ กรุณาอัปเกรดไปยังเวอร์ชั่นขั้นต่ำที่ต้องการ: {{minimalDifyVersion}}', } export default translation diff --git a/web/i18n/th-TH/share-app.ts b/web/i18n/th-TH/share-app.ts index 290a8cc3f7..fd4a8f386c 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,8 @@ const translation = { moreThanMaxLengthLine: 'แถว {{rowIndex}}: ค่า {{varName}} ต้องไม่เกิน {{maxLength}} อักขระ', atLeastOne: 'โปรดป้อนอย่างน้อยหนึ่งแถวในไฟล์ที่อัปโหลด', }, + execution: 'การดำเนินการ', + executions: '{{num}} การประหารชีวิต', }, } 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/workflow.ts b/web/i18n/th-TH/workflow.ts index 5cf4ad9e16..6b91a03a41 100644 --- a/web/i18n/th-TH/workflow.ts +++ b/web/i18n/th-TH/workflow.ts @@ -106,6 +106,15 @@ const translation = { addFailureBranch: 'เพิ่มสาขา Fail', loadMore: 'โหลดเวิร์กโฟลว์เพิ่มเติม', noHistory: 'ไม่มีประวัติ', + versionHistory: 'ประวัติรุ่น', + exportPNG: 'ส่งออกเป็น PNG', + noExist: 'ไม่มีตัวแปรดังกล่าว', + exportJPEG: 'ส่งออกเป็น JPEG', + referenceVar: 'ตัวแปรอ้างอิง', + publishUpdate: 'เผยแพร่การอัปเดต', + exitVersions: 'ออกเวอร์ชัน', + exportImage: 'ส่งออกภาพ', + exportSVG: 'ส่งออกเป็น SVG', }, env: { envPanelTitle: 'ตัวแปรสภาพแวดล้อม', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'การทดสอบการทําซ้ํา', back: 'ย้อนกลับ', iteration: 'เกิด ซ้ำ', + loop: 'ลูป', }, tabs: { 'searchBlock': 'บล็อกการค้นหา', @@ -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: 'ซูมเข้า', @@ -404,6 +419,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 +459,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 +574,8 @@ const translation = { 'all of': 'ทั้งหมด', 'exists': 'อยู่', 'not exists': 'ไม่มีอยู่จริง', + 'before': 'ก่อน', + 'after': 'หลังจากนั้น', }, optionName: { image: 'ภาพ', @@ -519,6 +591,7 @@ const translation = { selectVariable: 'เลือกตัวแปร...', addSubVariable: 'ตัวแปรย่อย', select: 'เลือก', + condition: 'เงื่อนไข', }, variableAssigner: { title: 'กําหนดตัวแปร', @@ -561,6 +634,8 @@ const translation = { 'over-write': 'เขียน ทับ', '+=': '+=', '/=': '/=', + 'remove-last': 'ลบสุดท้าย', + 'remove-first': 'ลบอันดับแรก', }, 'noAssignedVars': 'ไม่มีตัวแปรที่กําหนด', 'selectAssignedVariable': 'เลือกตัวแปรที่กําหนด...', @@ -765,6 +840,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 +883,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.ts b/web/i18n/tr-TR/app.ts index f205bd8ae4..0e27f84582 100644 --- a/web/i18n/tr-TR/app.ts +++ b/web/i18n/tr-TR/app.ts @@ -159,6 +159,10 @@ 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ğı', @@ -190,6 +194,17 @@ 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.', + }, } 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..584c0a8096 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', @@ -157,6 +161,9 @@ const translation = { community: 'Topluluk', about: 'Hakkında', logout: 'Çıkış Yap', + support: 'Destek', + compliance: 'Uygunluk', + github: 'GitHub', }, settings: { accountGroup: 'HESAP', @@ -206,6 +213,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 +469,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ı', @@ -547,6 +557,7 @@ const translation = { inputPlaceholder: 'Bot 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', @@ -637,6 +648,25 @@ const translation = { 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ı', + }, } export default translation diff --git a/web/i18n/tr-TR/custom.ts b/web/i18n/tr-TR/custom.ts index d4526074d5..15c4ff59ca 100644 --- a/web/i18n/tr-TR/custom.ts +++ b/web/i18n/tr-TR/custom.ts @@ -3,6 +3,8 @@ 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', 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/plugin.ts b/web/i18n/tr-TR/plugin.ts index 4bab35950b..c434052081 100644 --- a/web/i18n/tr-TR/plugin.ts +++ b/web/i18n/tr-TR/plugin.ts @@ -180,6 +180,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', @@ -204,6 +206,10 @@ 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}}', } export default translation diff --git a/web/i18n/tr-TR/share-app.ts b/web/i18n/tr-TR/share-app.ts index 26c6f56fb4..184f44e147 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/tr-TR/workflow.ts index 0ba1206b86..999456f1cf 100644 --- a/web/i18n/tr-TR/workflow.ts +++ b/web/i18n/tr-TR/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Çevre Değişkenleri', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Test Çalıştırma Yineleme', back: 'Geri', iteration: 'Yineleme', + loop: 'Döngü', }, tabs: { 'searchBlock': 'Blok ara', @@ -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', @@ -404,6 +419,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 +459,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 +576,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 +593,7 @@ const translation = { }, addSubVariable: 'Alt Değişken', select: 'Seçmek', + condition: 'Koşul', }, variableAssigner: { title: 'Değişken ata', @@ -563,6 +636,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...', @@ -767,6 +842,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 +885,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.ts b/web/i18n/uk-UA/app.ts index 2a9c03eace..09df6bf413 100644 --- a/web/i18n/uk-UA/app.ts +++ b/web/i18n/uk-UA/app.ts @@ -159,6 +159,10 @@ const translation = { title: 'Опік', description: 'Opik — це платформа з відкритим вихідним кодом для оцінки, тестування та моніторингу додатків LLM.', }, + weave: { + title: 'Ткати', + description: 'Weave є платформою з відкритим кодом для оцінки, тестування та моніторингу LLM додатків.', + }, }, answerIcon: { title: 'Використовуйте піктограму WebApp для заміни 🤖', @@ -194,6 +198,17 @@ const translation = { params: 'ПАРАМЕТРИ ПРОГРАМИ', placeholder: 'Виберіть програму...', }, + structOutput: { + LLMResponse: 'Відповідь ЛЛМ', + configure: 'Налаштувати', + required: 'Необхідно', + moreFillTip: 'Показуючи максимум 10 рівнів вкладеності', + structured: 'Структурований', + modelNotSupported: 'Модель не підтримується', + notConfiguredTip: 'Структурований вихід ще не було налаштовано', + modelNotSupportedTip: 'Поточна модель не підтримує цю функцію та автоматично знижується до ін\'єкції запитів.', + structuredTip: 'Структуровані виходи - це функція, яка забезпечує, що модель завжди генеруватиме відповіді, що відповідають наданій вами схемі JSON.', + }, } 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..dee558ad44 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: 'Будь ласка, введіть текст', @@ -153,6 +157,9 @@ const translation = { community: 'Спільнота', about: 'Про нас', logout: 'Вийти', + compliance: 'Відповідність', + support: 'Підтримка', + github: 'Гітхаб', }, settings: { accountGroup: 'ОБЛІКОВИЙ ЗАПИС', @@ -202,6 +209,9 @@ const translation = { deleteSuccessTip: 'Вашому обліковому запису потрібен час, щоб завершити видалення. Ми надішлемо вам електронного листа, коли все буде готово.', deleteLabel: 'Для підтвердження, будь ласка, введіть свою електронну пошту нижче', deletePlaceholder: 'Будь ласка, введіть свою електронну пошту', + workspaceName: 'Назва робочого простору', + workspaceIcon: 'Іконка робочого простору', + editWorkspaceInfo: 'Редагувати інформацію про робочий простір', }, members: { team: 'Команда', @@ -456,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/extension/api-based-extension/README', add: 'Додати розширення API', selector: { title: 'Розширення API', @@ -544,6 +554,7 @@ const translation = { inputPlaceholder: 'Поговоріть з ботом', thought: 'Думка', thinking: 'Мислення...', + resend: 'Відправити знову', }, promptEditor: { placeholder: 'Пишіть свої підказки тут, вводьте \'{\', щоб вставити змінну чи \'/\', щоб вставити блок-підказку', @@ -638,6 +649,25 @@ const translation = { 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: 'Перетягніть зображення сюди або', + }, } export default translation diff --git a/web/i18n/uk-UA/custom.ts b/web/i18n/uk-UA/custom.ts index 378e094c62..1eba3f14c8 100644 --- a/web/i18n/uk-UA/custom.ts +++ b/web/i18n/uk-UA/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: 'Оновіть свій план до ', suffix: ', щоб налаштувати свій бренд.', + title: 'Оновіть свій план', + des: 'Оновіть свій план, щоб налаштувати свій бренд', }, webapp: { title: 'Налаштувати бренд для WebApp', 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/plugin.ts b/web/i18n/uk-UA/plugin.ts index c86e225b1e..465624f2d3 100644 --- a/web/i18n/uk-UA/plugin.ts +++ b/web/i18n/uk-UA/plugin.ts @@ -180,6 +180,8 @@ const translation = { difyMarketplace: 'Dify Marketplace', viewMore: 'Дивитись більше', noPluginFound: 'Плагін не знайдено', + verifiedTip: 'Перевірено Dify', + partnerTip: 'Перевірено партнером Dify', }, task: { installingWithError: 'Не вдалося встановити плагіни {{installingLength}}, успіх {{successLength}}, {{errorLength}}', @@ -204,6 +206,10 @@ const translation = { search: 'Шукати', searchPlugins: 'Плагіни пошуку', allCategories: 'Всі категорії', + metadata: { + title: 'Плагіни', + }, + difyVersionNotCompatible: 'Поточна версія Dify не сумісна з цим плагіном, будь ласка, оновіть до мінімальної версії: {{minimalDifyVersion}}', } export default translation diff --git a/web/i18n/uk-UA/share-app.ts b/web/i18n/uk-UA/share-app.ts index 3465a6e5b9..058925ff15 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,8 @@ const translation = { moreThanMaxLengthLine: 'Рядок {{rowIndex}}: значення {{varName}} не може містити більше {{maxLength}} символів', atLeastOne: 'Будь ласка, введіть принаймні один рядок у завантажений файл.', }, + execution: 'ВИКОНАННЯ', + executions: '{{num}} ВИКОНАНЬ', }, } 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/workflow.ts b/web/i18n/uk-UA/workflow.ts index a4b832660d..c2d0b137fb 100644 --- a/web/i18n/uk-UA/workflow.ts +++ b/web/i18n/uk-UA/workflow.ts @@ -106,6 +106,15 @@ const translation = { addFailureBranch: 'Додано гілку помилки', noHistory: 'Без історії', loadMore: 'Завантажте більше робочих процесів', + referenceVar: 'Посилальна змінна', + exportPNG: 'Експортувати як PNG', + noExist: 'Такої змінної не існує', + exitVersions: 'Вихідні версії', + versionHistory: 'Історія версій', + publishUpdate: 'Опублікувати оновлення', + exportImage: 'Експорт зображення', + exportSVG: 'Експортувати як SVG', + exportJPEG: 'Експортувати як JPEG', }, env: { envPanelTitle: 'Змінні середовища', @@ -205,6 +214,7 @@ const translation = { testRunIteration: 'Ітерація тестового запуску', back: 'Назад', iteration: 'Ітерація', + loop: 'Петля', }, tabs: { 'searchBlock': 'Пошук блоку', @@ -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: 'Збільшити', @@ -404,6 +419,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 +459,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 +575,8 @@ const translation = { 'exists': 'Існує', 'not exists': 'не існує', 'not in': 'Не в', + 'after': 'після', + 'before': 'раніше', }, enterValue: 'Введіть значення', addCondition: 'Додати умову', @@ -520,6 +592,7 @@ const translation = { }, select: 'Виберіть', addSubVariable: 'Підзмінна', + condition: 'Умова', }, variableAssigner: { title: 'Присвоєння змінних', @@ -562,6 +635,8 @@ const translation = { '+=': '+=', '*=': '*=', 'extend': 'Розширити', + 'remove-last': 'Видалити останнє', + 'remove-first': 'Видалити перший', }, 'selectAssignedVariable': 'Виберіть призначену змінну...', 'noAssignedVars': 'Немає доступних призначених змінних', @@ -766,6 +841,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 +884,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.ts b/web/i18n/vi-VN/app.ts index fd0a66ad03..aacfc6419b 100644 --- a/web/i18n/vi-VN/app.ts +++ b/web/i18n/vi-VN/app.ts @@ -159,6 +159,10 @@ 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', @@ -194,6 +198,17 @@ 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', + }, } 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..6023fe7285 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', @@ -153,6 +157,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 +209,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 +465,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', @@ -543,6 +553,7 @@ const translation = { inputPlaceholder: 'Nói chuyện với Bot', 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ở', @@ -637,6 +648,25 @@ const translation = { 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', + }, } export default translation diff --git a/web/i18n/vi-VN/custom.ts b/web/i18n/vi-VN/custom.ts index 6c91e519b0..6f9a4720c4 100644 --- a/web/i18n/vi-VN/custom.ts +++ b/web/i18n/vi-VN/custom.ts @@ -3,6 +3,8 @@ 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', 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/plugin.ts b/web/i18n/vi-VN/plugin.ts index 512e27b6a5..5e5147c69e 100644 --- a/web/i18n/vi-VN/plugin.ts +++ b/web/i18n/vi-VN/plugin.ts @@ -180,6 +180,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', @@ -204,6 +206,10 @@ 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}}', } export default translation diff --git a/web/i18n/vi-VN/share-app.ts b/web/i18n/vi-VN/share-app.ts index 7078ecc299..a55f9b8476 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,8 @@ 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', }, } 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/workflow.ts b/web/i18n/vi-VN/workflow.ts index db56350daf..18ae72022e 100644 --- a/web/i18n/vi-VN/workflow.ts +++ b/web/i18n/vi-VN/workflow.ts @@ -106,6 +106,15 @@ 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', }, env: { envPanelTitle: 'Biến Môi Trường', @@ -205,6 +214,7 @@ 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', @@ -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', @@ -404,6 +419,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 +459,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 +575,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 +592,7 @@ const translation = { }, addSubVariable: 'Biến phụ', select: 'Lựa', + condition: 'Điều kiện', }, variableAssigner: { title: 'Gán biến', @@ -562,6 +635,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...', @@ -766,6 +841,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 +884,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.ts b/web/i18n/zh-Hans/app.ts index 7ef8c1b514..c1723fcab9 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -172,6 +172,10 @@ const translation = { removeConfirmTitle: '删除 {{key}} 配置?', removeConfirmContent: '当前配置正在使用中,删除它将关闭追踪功能。', }, + weave: { + title: '编织', + description: 'Weave是一个开源平台,用于评估、测试和监控大型语言模型应用程序。', + }, }, appSelector: { label: '应用', diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index e5fbff77b0..c00d137aca 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -184,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..9ed961feab 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}} 为必填项', @@ -475,7 +482,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 扩展', diff --git a/web/i18n/zh-Hans/dataset-creation.ts b/web/i18n/zh-Hans/dataset-creation.ts index aec029be2e..72e2511bd4 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', @@ -100,6 +100,10 @@ const translation = { scrapTimeInfo: '总共在 {{time}}秒 内抓取了 {{total}} 个页面', preview: '预览', maxDepthTooltip: '相对于输入 URL 的最大抓取深度。深度0仅抓取输入 URL 本身的页面,深度1抓取输入 URL 及其后的一层目录(一个 /),依此类推。', + watercrawlDocLink: '从网站同步', + watercrawlDoc: 'Watercrawl 文档', + configureWatercrawl: '配置水爬行', + watercrawlTitle: '使用Watercrawl提取网页内容', }, }, stepTwo: { 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/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/share-app.ts b/web/i18n/zh-Hans/share-app.ts index bfd17ef7a3..3e89aec041 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: { 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/workflow.ts b/web/i18n/zh-Hans/workflow.ts index ab56c468ce..ab77e0ef8d 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -638,6 +638,8 @@ const translation = { 'clear': '清空', 'extend': '扩展', 'append': '追加', + 'remove-first': '移除首项', + 'remove-last': '移除末项', '+=': '+=', '-=': '-=', '*=': '*=', @@ -737,6 +739,9 @@ const translation = { loop_one: '{{count}} 个循环', loop_other: '{{count}} 个循环', currentLoop: '当前循环', + comma: ',', + error_one: '{{count}}个失败', + error_other: '{{count}}个失败', breakCondition: '循环终止条件', breakConditionTip: '支持引用终止条件循环内的变量和会话变量。', loopMaxCount: '最大循环次数', diff --git a/web/i18n/zh-Hant/app.ts b/web/i18n/zh-Hant/app.ts index 44412bfcca..f49adc84bc 100644 --- a/web/i18n/zh-Hant/app.ts +++ b/web/i18n/zh-Hant/app.ts @@ -158,6 +158,10 @@ const translation = { title: '奧皮克', description: 'Opik 是一個用於評估、測試和監控 LLM 應用程式的開源平臺。', }, + weave: { + title: '編織', + description: 'Weave 是一個開源平台,用於評估、測試和監控大型語言模型應用程序。', + }, }, answerIcon: { descriptionInExplore: '是否使用 WebApp 圖示在 Explore 中取代 🤖', @@ -193,6 +197,17 @@ const translation = { params: '應用程式參數', label: '應用程式', }, + structOutput: { + moreFillTip: '顯示最多10層的嵌套', + required: '必需的', + LLMResponse: 'LLM 回應', + structured: '結構化的', + configure: '配置', + modelNotSupported: '模型不支持', + modelNotSupportedTip: '當前模型不支持此功能,並自動降級為提示注入。', + structuredTip: '結構化輸出是一項功能,確保模型始終生成符合您提供的 JSON 架構的響應。', + notConfiguredTip: '結構化輸出尚未配置', + }, } export default translation diff --git a/web/i18n/zh-Hant/billing.ts b/web/i18n/zh-Hant/billing.ts index 208102c5be..f3ce5ec79c 100644 --- a/web/i18n/zh-Hant/billing.ts +++ b/web/i18n/zh-Hant/billing.ts @@ -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 免費試用', 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..84b7bc53c2 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: '請輸入', @@ -153,6 +157,9 @@ const translation = { community: '社群', about: '關於', logout: '登出', + support: '支持', + github: 'GitHub', + compliance: '合規', }, settings: { accountGroup: '賬戶', @@ -202,6 +209,9 @@ const translation = { feedbackLabel: '告訴我們您刪除帳戶的原因?', feedbackPlaceholder: '自選', sendVerificationButton: '發送驗證碼', + workspaceName: '工作區名稱', + workspaceIcon: '工作區域圖示', + editWorkspaceInfo: '編輯工作區資訊', }, members: { team: '團隊', @@ -455,7 +465,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 擴充套件', @@ -543,6 +553,7 @@ const translation = { inputPlaceholder: '與 Bot 對話', thinking: '思維。。。', thought: '思想', + resend: '重新發送', }, promptEditor: { placeholder: '在這裡寫你的提示詞,輸入\'{\' 插入變數、輸入\'/\' 插入提示內容塊', @@ -637,6 +648,25 @@ const translation = { 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: '將您的圖片放在這裡,或', + }, } export default translation diff --git a/web/i18n/zh-Hant/custom.ts b/web/i18n/zh-Hant/custom.ts index 85b954c27a..bda56657f3 100644 --- a/web/i18n/zh-Hant/custom.ts +++ b/web/i18n/zh-Hant/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: '升級您的計劃以', suffix: '定製您的品牌。', + des: '升級您的計劃以自訂您的品牌', + title: '升級您的計劃', }, webapp: { title: '定製 WebApp 品牌', diff --git a/web/i18n/zh-Hant/dataset-creation.ts b/web/i18n/zh-Hant/dataset-creation.ts index be183ae72f..ca2c410ac6 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: { @@ -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-settings.ts b/web/i18n/zh-Hant/dataset-settings.ts index 768937c168..3dce646e7e 100644 --- a/web/i18n/zh-Hant/dataset-settings.ts +++ b/web/i18n/zh-Hant/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: '瞭解更多', description: '關於檢索方法。', longDescription: '關於檢索方法,您可以隨時在知識庫設定中更改此設定。', + method: '檢索方法', }, save: '儲存', permissionsInvitedMembers: '部分團隊成員', diff --git a/web/i18n/zh-Hant/dataset.ts b/web/i18n/zh-Hant/dataset.ts index 6a0d9cab21..ce4d63a54b 100644 --- a/web/i18n/zh-Hant/dataset.ts +++ b/web/i18n/zh-Hant/dataset.ts @@ -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..9637324b85 --- /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..c0f4a51e30 100644 --- a/web/i18n/zh-Hant/explore.ts +++ b/web/i18n/zh-Hant/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: '人力資源', Agent: '代理', Workflow: '工作流', + Entertainment: '娛樂', }, } diff --git a/web/i18n/zh-Hant/plugin.ts b/web/i18n/zh-Hant/plugin.ts index 1f71611265..03f6ece7e5 100644 --- a/web/i18n/zh-Hant/plugin.ts +++ b/web/i18n/zh-Hant/plugin.ts @@ -180,6 +180,8 @@ const translation = { viewMore: '查看更多', difyMarketplace: 'Dify 市場', pluginsResult: '{{num}} 個結果', + verifiedTip: '由Dify驗證', + partnerTip: '由 Dify 合作夥伴驗證', }, task: { installingWithError: '安裝 {{installingLength}} 個插件,{{successLength}} 成功,{{errorLength}} 失敗', @@ -204,6 +206,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..bf81582d58 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: { @@ -64,6 +70,8 @@ const translation = { moreThanMaxLengthLine: '第 {{rowIndex}} 行: {{varName}}值超過最大長度 {{maxLength}}', atLeastOne: '上傳檔案的內容不能少於一條', }, + execution: '執行', + executions: '{{num}} 執行', }, } 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/workflow.ts b/web/i18n/zh-Hant/workflow.ts index 1e4fd2ef21..9a4d7f25d8 100644 --- a/web/i18n/zh-Hant/workflow.ts +++ b/web/i18n/zh-Hant/workflow.ts @@ -106,6 +106,15 @@ const translation = { addFailureBranch: '添加 Fail Branch', loadMore: '載入更多工作流', noHistory: '無歷史記錄', + publishUpdate: '發布更新', + referenceVar: '參考變量', + exportSVG: '匯出為SVG', + exportPNG: '匯出為 PNG', + noExist: '沒有這個變數', + versionHistory: '版本歷史', + exitVersions: '退出版本', + exportImage: '匯出圖像', + exportJPEG: '匯出為JPEG', }, env: { envPanelTitle: '環境變數', @@ -205,6 +214,7 @@ const translation = { testRunIteration: '測試運行迭代', back: '返回', iteration: '迭代', + loop: '循環', }, tabs: { 'searchBlock': '搜索節點', @@ -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: '放大', @@ -406,6 +419,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 +459,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 +575,8 @@ const translation = { 'in': '在', 'not in': '不在', 'not exists': '不存在', + 'after': '之後', + 'before': '之前', }, enterValue: '輸入值', addCondition: '添加條件', @@ -522,6 +592,7 @@ const translation = { }, select: '選擇', addSubVariable: '子變數', + condition: '條件', }, variableAssigner: { title: '變量賦值', @@ -564,6 +635,8 @@ const translation = { '-=': '-=', 'append': '附加', 'clear': '清除', + 'remove-first': '移除首項', + 'remove-last': '移除末項', }, 'noAssignedVars': '沒有可用的已分配變數', 'variables': '變數', @@ -768,6 +841,38 @@ const translation = { 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 +884,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 7eb5f37f21..c5c6938113 100644 --- a/web/middleware.ts +++ b/web/middleware.ts @@ -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/next.config.js b/web/next.config.js index 4243ffecdf..9ce1b35644 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -13,6 +13,12 @@ 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, @@ -24,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 960812f5a3..3da5b54921 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "dify-web", - "version": "1.3.0", + "version": "1.4.0", "private": true, "engines": { "node": ">=v22.11.0" @@ -36,13 +36,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", @@ -84,7 +84,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", @@ -220,5 +220,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 4d48af8742..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 @@ -185,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 @@ -391,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) @@ -1289,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] @@ -1301,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] @@ -1313,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] @@ -1325,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] @@ -1337,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] @@ -1349,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] @@ -1361,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] @@ -1373,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] @@ -1385,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] @@ -1397,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] @@ -1409,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] @@ -1421,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] @@ -1433,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] @@ -1445,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] @@ -1457,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] @@ -1469,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] @@ -1481,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] @@ -1493,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] @@ -1505,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] @@ -1517,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] @@ -1529,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] @@ -1541,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] @@ -1553,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] @@ -1565,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] @@ -1577,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] @@ -1977,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' @@ -4649,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 @@ -6100,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==} @@ -9627,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': @@ -10329,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': @@ -10492,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 @@ -10572,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': @@ -10726,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 @@ -10736,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 @@ -11114,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 @@ -11122,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: @@ -11164,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 @@ -11212,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) @@ -11227,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 @@ -11258,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' @@ -11277,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 @@ -11292,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: @@ -11307,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 @@ -11317,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 @@ -12388,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: @@ -12967,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 @@ -12978,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: @@ -13534,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: @@ -14378,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 @@ -14393,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: {} @@ -14773,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 @@ -14781,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: @@ -15557,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: @@ -16351,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 @@ -16378,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: {} @@ -16747,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 @@ -17513,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: @@ -17880,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: @@ -17980,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: @@ -18514,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 @@ -18522,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: @@ -18534,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 @@ -18556,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 8aaf94f955..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 = ` @@ -22,6 +23,53 @@ `; + + 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 @@ -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}`; @@ -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; diff --git a/web/public/embed.min.js b/web/public/embed.min.js index c0aeac4487..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{"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=` + `),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=` - `,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,l=("touchstart"===e.type?(s=e.touches[0].clientX-r.offsetLeft,d=e.touches[0].clientY-r.offsetTop,t=e.touches[0].clientX,e.touches[0]):(s=e.clientX-r.offsetLeft,d=e.clientY-r.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-l;if(u=8{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{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{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{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 @@ + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + diff --git a/web/themes/dark.css b/web/themes/dark.css index 5b191d1a3d..edbd9de7b0 100644 --- a/web/themes/dark.css +++ b/web/themes/dark.css @@ -736,4 +736,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..1d96658963 100644 --- a/web/themes/light.css +++ b/web/themes/light.css @@ -736,4 +736,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..881f9d2c47 100644 --- a/web/themes/manual-dark.css +++ b/web/themes/manual-dark.css @@ -1,64 +1,65 @@ 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%); - --color-chat-bubble-bg: linear-gradient(180deg, - rgba(200, 206, 218, 0.08) 0%, - rgba(200, 206, 218, 0.02) 100%); - --color-chat-input-mask: linear-gradient(180deg, - rgba(24, 24, 27, 0.04) 0%, - rgba(24, 24, 27, 0.60) 100%); - --color-workflow-process-bg: linear-gradient(90deg, - rgba(24, 24, 27, 0.25) 0%, - rgba(24, 24, 27, 0.04) 100%); - --color-workflow-run-failed-bg: linear-gradient(98deg, - rgba(240, 68, 56, 0.12) 0%, - rgba(0, 0, 0, 0) 26.01%); - --color-workflow-batch-failed-bg: linear-gradient(92deg, - rgba(240, 68, 56, 0.3) 0%, - rgba(0, 0, 0, 0) 100%); - --color-marketplace-divider-bg: linear-gradient(90deg, - rgba(200, 206, 218, 0.14) 0%, - rgba(0, 0, 0, 0) 100%); - --color-marketplace-plugin-empty: linear-gradient(180deg, - rgba(0, 0, 0, 0) 0%, - #222225 100%); - --color-toast-success-bg: linear-gradient(92deg, - rgba(23, 178, 106, 0.3) 0%, - rgba(0, 0, 0, 0) 100%); - --color-toast-warning-bg: linear-gradient(92deg, - rgba(247, 144, 9, 0.3) 0%, - rgba(0, 0, 0, 0) 100%); - --color-toast-error-bg: linear-gradient(92deg, - rgba(240, 68, 56, 0.3) 0%, - rgba(0, 0, 0, 0) 100%); - --color-toast-info-bg: linear-gradient(92deg, - rgba(11, 165, 236, 0.3) 0%); - --color-account-teams-bg: linear-gradient(271deg, - rgba(34, 34, 37, 0.9) -0.1%, - rgba(29, 29, 32, 0.9) 98.26%); - --color-app-detail-bg: linear-gradient(169deg, - #1D1D20 1.18%, - #222225 99.52%); - --color-app-detail-overlay-bg: linear-gradient(270deg, - rgba(0, 0, 0, 0.00) 0%, - rgba(24, 24, 27, 0.02) 8%, - rgba(24, 24, 27, 0.54) 100%); - --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); - --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); - --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #1D1D20 0%, #222225 100%); - --color-dataset-child-chunk-expand-btn-bg: linear-gradient(90deg, rgba(24, 24, 27, 0.25) 0%, rgba(24, 24, 27, 0.04) 100%); - --color-dataset-option-card-blue-gradient: linear-gradient(90deg, #24252E 0%, #1E1E21 100%); - --color-dataset-option-card-purple-gradient: linear-gradient(90deg, #25242E 0%, #1E1E21 100%); - --color-dataset-option-card-orange-gradient: linear-gradient(90deg, #2B2322 0%, #1E1E21 100%); - --color-dataset-chunk-list-mask-bg: linear-gradient(180deg, rgba(34, 34, 37, 0.00) 0%, #222225 100%); - --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-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%); + --color-chatbot-bg: linear-gradient(180deg, + rgba(34, 34, 37, 0.9) 0%, + rgba(29, 29, 32, 0.9) 90.48%); + --color-chat-bubble-bg: linear-gradient(180deg, + rgba(200, 206, 218, 0.08) 0%, + rgba(200, 206, 218, 0.02) 100%); + --color-chat-input-mask: linear-gradient(180deg, + rgba(24, 24, 27, 0.04) 0%, + rgba(24, 24, 27, 0.60) 100%); + --color-workflow-process-bg: linear-gradient(90deg, + rgba(24, 24, 27, 0.25) 0%, + rgba(24, 24, 27, 0.04) 100%); + --color-workflow-run-failed-bg: linear-gradient(98deg, + rgba(240, 68, 56, 0.12) 0%, + rgba(0, 0, 0, 0) 26.01%); + --color-workflow-batch-failed-bg: linear-gradient(92deg, + rgba(240, 68, 56, 0.3) 0%, + rgba(0, 0, 0, 0) 100%); + --color-marketplace-divider-bg: linear-gradient(90deg, + rgba(200, 206, 218, 0.14) 0%, + rgba(0, 0, 0, 0) 100%); + --color-marketplace-plugin-empty: linear-gradient(180deg, + rgba(0, 0, 0, 0) 0%, + #222225 100%); + --color-toast-success-bg: linear-gradient(92deg, + rgba(23, 178, 106, 0.3) 0%, + rgba(0, 0, 0, 0) 100%); + --color-toast-warning-bg: linear-gradient(92deg, + rgba(247, 144, 9, 0.3) 0%, + rgba(0, 0, 0, 0) 100%); + --color-toast-error-bg: linear-gradient(92deg, + rgba(240, 68, 56, 0.3) 0%, + rgba(0, 0, 0, 0) 100%); + --color-toast-info-bg: linear-gradient(92deg, + rgba(11, 165, 236, 0.3) 0%); + --color-account-teams-bg: linear-gradient(271deg, + rgba(34, 34, 37, 0.9) -0.1%, + rgba(29, 29, 32, 0.9) 98.26%); + --color-app-detail-bg: linear-gradient(169deg, + #1D1D20 1.18%, + #222225 99.52%); + --color-app-detail-overlay-bg: linear-gradient(270deg, + rgba(0, 0, 0, 0.00) 0%, + rgba(24, 24, 27, 0.02) 8%, + rgba(24, 24, 27, 0.54) 100%); + --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); + --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); + --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #1D1D20 0%, #222225 100%); + --color-dataset-child-chunk-expand-btn-bg: linear-gradient(90deg, rgba(24, 24, 27, 0.25) 0%, rgba(24, 24, 27, 0.04) 100%); + --color-dataset-option-card-blue-gradient: linear-gradient(90deg, #24252E 0%, #1E1E21 100%); + --color-dataset-option-card-purple-gradient: linear-gradient(90deg, #25242E 0%, #1E1E21 100%); + --color-dataset-option-card-orange-gradient: linear-gradient(90deg, #2B2322 0%, #1E1E21 100%); + --color-dataset-chunk-list-mask-bg: linear-gradient(180deg, rgba(34, 34, 37, 0.00) 0%, #222225 100%); + --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%); +} diff --git a/web/themes/manual-light.css b/web/themes/manual-light.css index 92473320bc..ab6f14423f 100644 --- a/web/themes/manual-light.css +++ b/web/themes/manual-light.css @@ -1,64 +1,65 @@ 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-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%); + --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%); +} 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/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 +]