From a4a67ef1ece17dde495cad7a1616ea48bff536c1 Mon Sep 17 00:00:00 2001 From: doskoi <50610194+t-daisuke@users.noreply.github.com> Date: Sun, 20 Jul 2025 22:22:15 +0900 Subject: [PATCH 01/18] fix(i18n): improve Japanese translations for technical terms "dupulicate" (#22669) --- web/app/(commonLayout)/datasets/template/template.ja.mdx | 8 ++++---- web/i18n/ja-JP/common.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/app/(commonLayout)/datasets/template/template.ja.mdx b/web/app/(commonLayout)/datasets/template/template.ja.mdx index 23f78b5d7d..6c0e20e1bb 100644 --- a/web/app/(commonLayout)/datasets/template/template.ja.mdx +++ b/web/app/(commonLayout)/datasets/template/template.ja.mdx @@ -83,7 +83,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - subchunk_segmentation (object) 子チャンクルール - separator セグメンテーション識別子。現在は 1 つの区切り文字のみ許可。デフォルトは *** - max_tokens 最大長 (トークン) は親チャンクの長さより短いことを検証する必要があります - - chunk_overlap 隣接するチャンク間の重複を定義 (オプション) + - chunk_overlap 隣接するチャンク間の重なりを定義 (オプション) ナレッジベースにパラメータが設定されていない場合、最初のアップロードには以下のパラメータを提供する必要があります。提供されない場合、デフォルトパラメータが使用されます。 @@ -218,7 +218,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - subchunk_segmentation (object) 子チャンクルール - separator セグメンテーション識別子。現在は 1 つの区切り文字のみ許可。デフォルトは *** - max_tokens 最大長 (トークン) は親チャンクの長さより短いことを検証する必要があります - - chunk_overlap 隣接するチャンク間の重複を定義 (オプション) + - chunk_overlap 隣接するチャンク間の重なりを定義 (オプション) アップロードする必要があるファイル。 @@ -555,7 +555,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - subchunk_segmentation (object) 子チャンクルール - separator セグメンテーション識別子。現在は 1 つの区切り文字のみ許可。デフォルトは *** - max_tokens 最大長 (トークン) は親チャンクの長さより短いことを検証する必要があります - - chunk_overlap 隣接するチャンク間の重複を定義 (オプション) + - chunk_overlap 隣接するチャンク間の重なりを定義 (オプション) @@ -657,7 +657,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - subchunk_segmentation (object) 子チャンクルール - separator セグメンテーション識別子。現在は 1 つの区切り文字のみ許可。デフォルトは *** - max_tokens 最大長 (トークン) は親チャンクの長さより短いことを検証する必要があります - - chunk_overlap 隣接するチャンク間の重複を定義 (オプション) + - chunk_overlap 隣接するチャンク間の重なりを定義 (オプション) diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 74c84e616a..5328bbfbd9 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -43,7 +43,7 @@ const translation = { log: 'ログ', learnMore: '詳細はこちら', params: 'パラメータ', - duplicate: '重複', + duplicate: '複製', rename: '名前の変更', audioSourceUnavailable: 'AudioSource が利用できません', zoomIn: 'ズームインする', From cb660e81044c98e14acee66ccf3d54ae9a315d64 Mon Sep 17 00:00:00 2001 From: doskoi <50610194+t-daisuke@users.noreply.github.com> Date: Sun, 20 Jul 2025 22:22:30 +0900 Subject: [PATCH 02/18] fix(i18n): standardize template variable names across all languages {{count}} (#22670) --- web/i18n/de-DE/app.ts | 4 ++-- web/i18n/fa-IR/app.ts | 4 ++-- web/i18n/hi-IN/app.ts | 4 ++-- web/i18n/ja-JP/app.ts | 4 ++-- web/i18n/ko-KR/app.ts | 4 ++-- web/i18n/pl-PL/app.ts | 4 ++-- web/i18n/ro-RO/app.ts | 4 ++-- web/i18n/ru-RU/app.ts | 4 ++-- web/i18n/sl-SI/app.ts | 4 ++-- web/i18n/th-TH/app.ts | 4 ++-- web/i18n/tr-TR/app.ts | 4 ++-- web/i18n/vi-VN/app.ts | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/web/i18n/de-DE/app.ts b/web/i18n/de-DE/app.ts index 95f2722640..c28fcb2be5 100644 --- a/web/i18n/de-DE/app.ts +++ b/web/i18n/de-DE/app.ts @@ -78,7 +78,7 @@ const translation = { optional: 'Wahlfrei', noTemplateFound: 'Keine Vorlagen gefunden', workflowUserDescription: 'Autonome KI-Arbeitsabläufe visuell per Drag-and-Drop erstellen.', - foundResults: '{{Anzahl}} Befund', + foundResults: '{{count}} Befund', chatbotShortDescription: 'LLM-basierter Chatbot mit einfacher Einrichtung', completionUserDescription: 'Erstellen Sie schnell einen KI-Assistenten für Textgenerierungsaufgaben mit einfacher Konfiguration.', noAppsFound: 'Keine Apps gefunden', @@ -92,7 +92,7 @@ const translation = { noTemplateFoundTip: 'Versuchen Sie, mit verschiedenen Schlüsselwörtern zu suchen.', advancedUserDescription: 'Workflow mit Speicherfunktionen und Chatbot-Oberfläche.', chatbotUserDescription: 'Erstellen Sie schnell einen LLM-basierten Chatbot mit einfacher Konfiguration. Sie können später zu Chatflow wechseln.', - foundResult: '{{Anzahl}} Ergebnis', + foundResult: '{{count}} Ergebnis', agentUserDescription: 'Ein intelligenter Agent, der in der Lage ist, iteratives Denken zu führen und autonome Werkzeuge zu verwenden, um Aufgabenziele zu erreichen.', agentShortDescription: 'Intelligenter Agent mit logischem Denken und autonomer Werkzeugnutzung', dropDSLToCreateApp: 'Ziehen Sie die DSL-Datei hierher, um die App zu erstellen', diff --git a/web/i18n/fa-IR/app.ts b/web/i18n/fa-IR/app.ts index 890dae5cae..d8dfba3d81 100644 --- a/web/i18n/fa-IR/app.ts +++ b/web/i18n/fa-IR/app.ts @@ -77,10 +77,10 @@ const translation = { appCreateDSLErrorPart1: 'تفاوت قابل توجهی در نسخه های DSL مشاهده شده است. اجبار به واردات ممکن است باعث اختلال در عملکرد برنامه شود.', appCreateDSLWarning: 'احتیاط: تفاوت نسخه DSL ممکن است بر ویژگی های خاصی تأثیر بگذارد', completionShortDescription: 'دستیار هوش مصنوعی برای تسک های تولید متن', - foundResult: '{{تعداد}} نتیجه', + foundResult: '{{count}} نتیجه', chatbotUserDescription: 'به سرعت یک چت بات مبتنی بر LLM با پیکربندی ساده بسازید. بعدا می توانید به Chatflow بروید.', chooseAppType: 'انتخاب نوع برنامه', - foundResults: '{{تعداد}} نتیجه', + foundResults: '{{count}} نتیجه', noIdeaTip: 'ایده ای ندارید؟ قالب های ما را بررسی کنید', forBeginners: 'انواع برنامه‌های پایه‌تر', noAppsFound: 'هیچ برنامه ای یافت نشد', diff --git a/web/i18n/hi-IN/app.ts b/web/i18n/hi-IN/app.ts index f1fd1a54fa..dcd5e54bdc 100644 --- a/web/i18n/hi-IN/app.ts +++ b/web/i18n/hi-IN/app.ts @@ -74,12 +74,12 @@ const translation = { appCreateDSLErrorPart2: 'क्या आप जारी रखना चाहते हैं?', learnMore: 'और जानो', forBeginners: 'नए उपयोगकर्ताओं के लिए बुनियादी ऐप प्रकार', - foundResults: '{{गिनती}} परिणाम', + foundResults: '{{count}} परिणाम', forAdvanced: 'उन्नत उपयोगकर्ताओं के लिए', agentUserDescription: 'पुनरावृत्त तर्क और स्वायत्त उपकरण में सक्षम एक बुद्धिमान एजेंट कार्य लक्ष्यों को प्राप्त करने के लिए उपयोग करता है।', optional: 'वैकल्पिक', chatbotShortDescription: 'सरल सेटअप के साथ एलएलएम-आधारित चैटबॉट', - foundResult: '{{गिनती}} परिणाम', + foundResult: '{{count}} परिणाम', completionUserDescription: 'सरल कॉन्फ़िगरेशन के साथ पाठ निर्माण कार्यों के लिए त्वरित रूप से AI सहायक बनाएं।', noIdeaTip: 'कोई विचार नहीं? हमारे टेम्प्लेट देखें', noTemplateFound: 'कोई टेम्पलेट नहीं मिला', diff --git a/web/i18n/ja-JP/app.ts b/web/i18n/ja-JP/app.ts index 7b6afc99f5..24d78f9324 100644 --- a/web/i18n/ja-JP/app.ts +++ b/web/i18n/ja-JP/app.ts @@ -83,7 +83,7 @@ const translation = { forBeginners: '初心者向けの基本的なアプリタイプ', noTemplateFoundTip: '別のキーワードを使用して検索してみてください。', agentShortDescription: '推論と自律的なツールの使用を備えたインテリジェントエージェント', - foundResults: '{{カウント}}業績', + foundResults: '{{count}}件の結果', noTemplateFound: 'テンプレートが見つかりません', noAppsFound: 'アプリが見つかりませんでした', workflowShortDescription: 'インテリジェントな自動化のためのエージェントフロー', @@ -91,7 +91,7 @@ const translation = { advancedUserDescription: '追加のメモリ機能とチャットボットインターフェースを備えたワークフロー', advancedShortDescription: 'メモリを使用した複雑なマルチターン対話のワークフロー', agentUserDescription: 'タスクの目標を達成するために反復的な推論と自律的なツールを使用できるインテリジェントエージェント。', - foundResult: '{{カウント}}結果', + foundResult: '{{count}}件の結果', forAdvanced: '上級ユーザー向け', chooseAppType: 'アプリタイプを選択', learnMore: '詳細情報', diff --git a/web/i18n/ko-KR/app.ts b/web/i18n/ko-KR/app.ts index 40c3183d91..bcc18e70f0 100644 --- a/web/i18n/ko-KR/app.ts +++ b/web/i18n/ko-KR/app.ts @@ -90,12 +90,12 @@ const translation = { noTemplateFound: '템플릿을 찾을 수 없습니다.', completionShortDescription: '텍스트 생성 작업을 위한 AI 도우미', learnMore: '더 알아보세요', - foundResults: '{{개수}} 결과', + foundResults: '{{count}} 결과', agentShortDescription: '추론 및 자율적인 도구 사용 기능이 있는 지능형 에이전트', advancedShortDescription: '다중 대화를 위해 강화된 워크플로우', noAppsFound: '앱을 찾을 수 없습니다.', - foundResult: '{{개수}} 결과', + foundResult: '{{count}} 결과', completionUserDescription: '간단한 구성으로 텍스트 생성 작업을 위한 AI 도우미를 빠르게 구축합니다.', chatbotUserDescription: diff --git a/web/i18n/pl-PL/app.ts b/web/i18n/pl-PL/app.ts index f5fec6caeb..040789424c 100644 --- a/web/i18n/pl-PL/app.ts +++ b/web/i18n/pl-PL/app.ts @@ -80,7 +80,7 @@ const translation = { appCreateDSLErrorPart1: 'Wykryto istotną różnicę w wersjach DSL. Wymuszenie importu może spowodować nieprawidłowe działanie aplikacji.', noTemplateFoundTip: 'Spróbuj wyszukać za pomocą różnych słów kluczowych.', noAppsFound: 'Nie znaleziono aplikacji', - foundResults: '{{liczba}} Wyniki', + foundResults: '{{count}} Wyniki', noTemplateFound: 'Nie znaleziono szablonów', chatbotUserDescription: 'Szybko zbuduj chatbota opartego na LLM z prostą konfiguracją. Możesz przełączyć się na Chatflow później.', optional: 'Fakultatywny', @@ -91,7 +91,7 @@ const translation = { completionShortDescription: 'Asystent AI do zadań generowania tekstu', noIdeaTip: 'Nie masz pomysłów? Sprawdź nasze szablony', forAdvanced: 'DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW', - foundResult: '{{liczba}} Wynik', + foundResult: '{{count}} Wynik', advancedShortDescription: 'Przepływ ulepszony dla wieloturowych czatów', learnMore: 'Dowiedz się więcej', chatbotShortDescription: 'Chatbot oparty na LLM z prostą konfiguracją', diff --git a/web/i18n/ro-RO/app.ts b/web/i18n/ro-RO/app.ts index a32b8c3c0f..791bbcbc7e 100644 --- a/web/i18n/ro-RO/app.ts +++ b/web/i18n/ro-RO/app.ts @@ -84,8 +84,8 @@ const translation = { advancedShortDescription: 'Flux de lucru îmbunătățit pentru conversații multi-tur', advancedUserDescription: 'Flux de lucru cu funcții suplimentare de memorie și interfață de chatbot.', noTemplateFoundTip: 'Încercați să căutați folosind cuvinte cheie diferite.', - foundResults: '{{număr}} Rezultatele', - foundResult: '{{număr}} Rezultat', + foundResults: '{{count}} Rezultatele', + foundResult: '{{count}} Rezultat', noIdeaTip: 'Nicio idee? Consultați șabloanele noastre', noAppsFound: 'Nu s-au găsit aplicații', workflowShortDescription: 'Flux agentic pentru automatizări inteligente', diff --git a/web/i18n/ru-RU/app.ts b/web/i18n/ru-RU/app.ts index 16bdfd9b4a..d12f25ed57 100644 --- a/web/i18n/ru-RU/app.ts +++ b/web/i18n/ru-RU/app.ts @@ -78,11 +78,11 @@ const translation = { appCreateDSLErrorPart1: 'Обнаружена существенная разница в версиях DSL. Принудительный импорт может привести к сбою в работе приложения.', learnMore: 'Подробнее', forAdvanced: 'ДЛЯ ПРОДВИНУТЫХ ПОЛЬЗОВАТЕЛЕЙ', - foundResults: '{{Количество}} Результаты', + foundResults: '{{count}} Результаты', optional: 'Необязательный', chatbotShortDescription: 'Чат-бот на основе LLM с простой настройкой', advancedShortDescription: 'Рабочий процесс, улучшенный для многоходовых чатов', - foundResult: '{{Количество}} Результат', + foundResult: '{{count}} Результат', workflowShortDescription: 'Агентный поток для интеллектуальных автоматизаций', advancedUserDescription: 'Рабочий процесс с дополнительными функциями памяти и интерфейсом чат-бота.', noAppsFound: 'Приложения не найдены', diff --git a/web/i18n/sl-SI/app.ts b/web/i18n/sl-SI/app.ts index a68b4128e1..cd6d1169a4 100644 --- a/web/i18n/sl-SI/app.ts +++ b/web/i18n/sl-SI/app.ts @@ -79,8 +79,8 @@ const translation = { advancedShortDescription: 'Potek dela izboljšan za večkratne pogovore', noAppsFound: 'Ni bilo najdenih aplikacij', agentShortDescription: 'Inteligentni agent z razmišljanjem in avtonomno uporabo orodij', - foundResult: '{{štetje}} Rezultat', - foundResults: '{{štetje}} Rezultati', + foundResult: '{{count}} Rezultat', + foundResults: '{{count}} Rezultati', noTemplateFoundTip: 'Poskusite iskati z različnimi ključnimi besedami.', optional: 'Neobvezno', forBeginners: 'Bolj osnovne vrste aplikacij', diff --git a/web/i18n/th-TH/app.ts b/web/i18n/th-TH/app.ts index d89193bded..af2f67bcc1 100644 --- a/web/i18n/th-TH/app.ts +++ b/web/i18n/th-TH/app.ts @@ -73,7 +73,7 @@ const translation = { appCreateDSLErrorPart4: 'เวอร์ชัน DSL ที่ระบบรองรับ:', appCreateFailed: 'สร้างโปรเจกต์ไม่สําเร็จ', learnMore: 'ศึกษาเพิ่มเติม', - foundResults: '{{นับ}} ผลลัพธ์', + foundResults: '{{count}} ผลลัพธ์', noTemplateFoundTip: 'ลองค้นหาโดยใช้คีย์เวิร์ดอื่น', chatbotShortDescription: 'แชทบอทที่ใช้ LLM พร้อมการตั้งค่าที่ง่ายดาย', optional: 'เสริม', @@ -83,7 +83,7 @@ const translation = { completionShortDescription: 'ผู้ช่วย AI สําหรับงานสร้างข้อความ', agentUserDescription: 'ตัวแทนอัจฉริยะที่สามารถให้เหตุผลซ้ําๆ และใช้เครื่องมืออัตโนมัติเพื่อให้บรรลุเป้าหมายของงาน', noIdeaTip: 'ไม่มีความคิด? ดูเทมเพลตของเรา', - foundResult: '{{นับ}} ผล', + foundResult: '{{count}} ผล', noAppsFound: 'ไม่พบแอป', workflowShortDescription: 'โฟลว์อัตโนมัติสำหรับระบบอัจฉริยะ', forAdvanced: 'สําหรับผู้ใช้ขั้นสูง', diff --git a/web/i18n/tr-TR/app.ts b/web/i18n/tr-TR/app.ts index 73fff0f217..1847af9cf4 100644 --- a/web/i18n/tr-TR/app.ts +++ b/web/i18n/tr-TR/app.ts @@ -72,11 +72,11 @@ const translation = { appCreateDSLErrorPart3: 'Geçerli uygulama DSL sürümü:', appCreateDSLErrorTitle: 'Sürüm Uyumsuzluğu', Confirm: 'Onaylamak', - foundResults: '{{sayı}} Sonuç -ları', + foundResults: '{{count}} Sonuç -ları', noAppsFound: 'Uygulama bulunamadı', chatbotUserDescription: 'Basit yapılandırmayla hızlı bir şekilde LLM tabanlı bir sohbet botu oluşturun. Daha sonra Chatflow\'a geçebilirsiniz.', optional: 'Opsiyonel', - foundResult: '{{sayı}} Sonuç', + foundResult: '{{count}} Sonuç', noTemplateFound: 'Şablon bulunamadı', workflowUserDescription: 'Sürükle-bırak kolaylığıyla görsel olarak otonom yapay zeka iş akışları oluşturun.', advancedUserDescription: 'Ek bellek özellikleri ve sohbet robotu arayüzü ile iş akışı.', diff --git a/web/i18n/vi-VN/app.ts b/web/i18n/vi-VN/app.ts index c3b5ed96b8..4100b52b36 100644 --- a/web/i18n/vi-VN/app.ts +++ b/web/i18n/vi-VN/app.ts @@ -80,13 +80,13 @@ const translation = { optional: 'Tùy chọn', advancedShortDescription: 'Quy trình làm việc cho các cuộc đối thoại nhiều lượt phức tạp với bộ nhớ', workflowUserDescription: 'Xây dựng trực quan quy trình AI tự động bằng kéo thả đơn giản.', - foundResults: '{{đếm}} Kết quả', + foundResults: '{{count}} Kết quả', chatbotUserDescription: 'Nhanh chóng xây dựng chatbot dựa trên LLM với cấu hình đơn giản. Bạn có thể chuyển sang Chatflow sau.', agentUserDescription: 'Một tác nhân thông minh có khả năng suy luận lặp đi lặp lại và sử dụng công cụ tự động để đạt được mục tiêu nhiệm vụ.', noIdeaTip: 'Không có ý tưởng? Kiểm tra các mẫu của chúng tôi', advancedUserDescription: 'Quy trình với tính năng bộ nhớ bổ sung và giao diện chatbot.', forAdvanced: 'DÀNH CHO NGƯỜI DÙNG NÂNG CAO', - foundResult: '{{đếm}} Kết quả', + foundResult: '{{count}} Kết quả', agentShortDescription: 'Quy trình nâng cao cho hội thoại nhiều lượt', noTemplateFound: 'Không tìm thấy mẫu', noAppsFound: 'Không tìm thấy ứng dụng nào', From bd2014d13b5936e0d154a644ef6aba0339eef50d Mon Sep 17 00:00:00 2001 From: doskoi <50610194+t-daisuke@users.noreply.github.com> Date: Sun, 20 Jul 2025 22:23:08 +0900 Subject: [PATCH 03/18] =?UTF-8?q?fix(i18n):=20"=E9=81=93=E5=85=B7"=20into?= =?UTF-8?q?=20"=E3=83=84=E3=83=BC=E3=83=AB"=20(#22666)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/i18n/ja-JP/plugin.ts | 6 +++--- web/i18n/ja-JP/workflow.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/i18n/ja-JP/plugin.ts b/web/i18n/ja-JP/plugin.ts index e39479ea74..a12de17a16 100644 --- a/web/i18n/ja-JP/plugin.ts +++ b/web/i18n/ja-JP/plugin.ts @@ -2,7 +2,7 @@ const translation = { category: { extensions: '拡張機能', all: 'すべて', - tools: '道具', + tools: 'ツール', bundles: 'バンドル', agents: 'エージェント戦略', models: 'モデル', @@ -11,7 +11,7 @@ const translation = { agent: 'エージェント戦略', model: 'モデル', bundle: 'バンドル', - tool: '道具', + tool: 'ツール', extension: '拡張', }, list: { @@ -60,7 +60,7 @@ const translation = { uninstalledTitle: 'ツールがインストールされていません', empty: 'ツールを追加するには「+」ボタンをクリックしてください。複数のツールを追加できます。', paramsTip1: 'LLM 推論パラメータを制御します。', - toolLabel: '道具', + toolLabel: 'ツール', unsupportedTitle: 'サポートされていないアクション', toolSetting: 'ツール設定', unsupportedMCPTool: '現在選択されているエージェント戦略プラグインのバージョンはMCPツールをサポートしていません。', diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 58cd2e3f58..035bba61a6 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -887,7 +887,7 @@ const translation = { modelNotSelected: 'モデルが選択されていません', toolNotAuthorizedTooltip: '{{tool}} 認可されていません', toolNotInstallTooltip: '{{tool}}はインストールされていません', - tools: '道具', + tools: 'ツール', learnMore: 'もっと学ぶ', configureModel: 'モデルを設定する', model: 'モデル', From f9f46bfcbe2f61ead3b9600a5fbd50588770688f Mon Sep 17 00:00:00 2001 From: doskoi <50610194+t-daisuke@users.noreply.github.com> Date: Mon, 21 Jul 2025 10:26:39 +0900 Subject: [PATCH 04/18] fix(i18n) update Japanese translation for "optional" (#22667) --- web/i18n/ja-JP/app.ts | 2 +- web/i18n/ja-JP/common.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/i18n/ja-JP/app.ts b/web/i18n/ja-JP/app.ts index 24d78f9324..e03e9e1177 100644 --- a/web/i18n/ja-JP/app.ts +++ b/web/i18n/ja-JP/app.ts @@ -79,7 +79,7 @@ const translation = { appCreateDSLErrorTitle: 'バージョンの非互換性', appCreateDSLWarning: '注意:DSL のバージョンの違いは、特定の機能に影響を与える可能性があります', appCreateDSLErrorPart1: 'DSL バージョンに大きな違いが検出されました。インポートを強制すると、アプリケーションが誤動作する可能性があります。', - optional: '随意', + optional: '任意', forBeginners: '初心者向けの基本的なアプリタイプ', noTemplateFoundTip: '別のキーワードを使用して検索してみてください。', agentShortDescription: '推論と自律的なツールの使用を備えたインテリジェントエージェント', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 5328bbfbd9..c346984932 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -229,7 +229,7 @@ const translation = { permanentlyDeleteButton: 'アカウントを完全に削除', feedbackTitle: 'フィードバック', feedbackLabel: 'アカウントを削除した理由を教えてください。', - feedbackPlaceholder: '随意', + feedbackPlaceholder: '任意', sendVerificationButton: '確認コードの送信', editWorkspaceInfo: 'ワークスペース情報を編集', workspaceName: 'ワークスペース名', From 17a8f1a0f10e94d8a957b9af426e63b8418ad742 Mon Sep 17 00:00:00 2001 From: Novice Date: Mon, 21 Jul 2025 09:28:47 +0800 Subject: [PATCH 05/18] fix: avoid using node_data.version for judgement tool node version (#22462) Co-authored-by: JzoNg --- api/core/workflow/nodes/agent/agent_node.py | 9 ++++++++- api/core/workflow/nodes/agent/entities.py | 4 ++++ api/core/workflow/nodes/node_mapping.py | 6 ++++++ api/core/workflow/nodes/tool/entities.py | 4 ++++ api/core/workflow/nodes/tool/tool_node.py | 8 +++++++- web/app/components/base/chat/chat/question.tsx | 2 +- web/app/components/workflow/nodes/agent/default.ts | 12 +++++++----- web/app/components/workflow/nodes/agent/types.ts | 1 + .../components/workflow/nodes/agent/use-config.ts | 4 ++-- web/app/components/workflow/nodes/tool/default.ts | 2 +- web/app/components/workflow/nodes/tool/types.ts | 1 + web/app/components/workflow/utils/workflow-init.ts | 4 ++-- 12 files changed, 44 insertions(+), 13 deletions(-) diff --git a/api/core/workflow/nodes/agent/agent_node.py b/api/core/workflow/nodes/agent/agent_node.py index a4616eda69..704eb6a3ac 100644 --- a/api/core/workflow/nodes/agent/agent_node.py +++ b/api/core/workflow/nodes/agent/agent_node.py @@ -270,7 +270,14 @@ class AgentNode(BaseNode): ) extra = tool.get("extra", {}) - runtime_variable_pool = variable_pool if self._node_data.version != "1" else None + + # This is an issue that caused problems before. + # Logically, we shouldn't use the node_data.version field for judgment + # But for backward compatibility with historical data + # this version field judgment is still preserved here. + runtime_variable_pool: VariablePool | None = None + if node_data.version != "1" or node_data.tool_node_version != "1": + runtime_variable_pool = variable_pool tool_runtime = ToolManager.get_agent_tool_runtime( self.tenant_id, self.app_id, entity, self.invoke_from, runtime_variable_pool ) diff --git a/api/core/workflow/nodes/agent/entities.py b/api/core/workflow/nodes/agent/entities.py index 075a41fb2f..11b11068e7 100644 --- a/api/core/workflow/nodes/agent/entities.py +++ b/api/core/workflow/nodes/agent/entities.py @@ -13,6 +13,10 @@ class AgentNodeData(BaseNodeData): agent_strategy_name: str agent_strategy_label: str # redundancy memory: MemoryConfig | None = None + # The version of the tool parameter. + # If this value is None, it indicates this is a previous version + # and requires using the legacy parameter parsing rules. + tool_node_version: str | None = None class AgentInput(BaseModel): value: Union[list[str], list[ToolSelector], Any] diff --git a/api/core/workflow/nodes/node_mapping.py b/api/core/workflow/nodes/node_mapping.py index ccfaec4a8c..294b47670b 100644 --- a/api/core/workflow/nodes/node_mapping.py +++ b/api/core/workflow/nodes/node_mapping.py @@ -73,6 +73,9 @@ NODE_TYPE_CLASSES_MAPPING: Mapping[NodeType, Mapping[str, type[BaseNode]]] = { }, NodeType.TOOL: { LATEST_VERSION: ToolNode, + # This is an issue that caused problems before. + # Logically, we shouldn't use two different versions to point to the same class here, + # but in order to maintain compatibility with historical data, this approach has been retained. "2": ToolNode, "1": ToolNode, }, @@ -123,6 +126,9 @@ NODE_TYPE_CLASSES_MAPPING: Mapping[NodeType, Mapping[str, type[BaseNode]]] = { }, NodeType.AGENT: { LATEST_VERSION: AgentNode, + # This is an issue that caused problems before. + # Logically, we shouldn't use two different versions to point to the same class here, + # but in order to maintain compatibility with historical data, this approach has been retained. "2": AgentNode, "1": AgentNode, }, diff --git a/api/core/workflow/nodes/tool/entities.py b/api/core/workflow/nodes/tool/entities.py index 88c5160d14..f0a44d919b 100644 --- a/api/core/workflow/nodes/tool/entities.py +++ b/api/core/workflow/nodes/tool/entities.py @@ -59,6 +59,10 @@ class ToolNodeData(BaseNodeData, ToolEntity): return typ tool_parameters: dict[str, ToolInput] + # The version of the tool parameter. + # If this value is None, it indicates this is a previous version + # and requires using the legacy parameter parsing rules. + tool_node_version: str | None = None @field_validator("tool_parameters", mode="before") @classmethod diff --git a/api/core/workflow/nodes/tool/tool_node.py b/api/core/workflow/nodes/tool/tool_node.py index c565ad15c1..140fe71f60 100644 --- a/api/core/workflow/nodes/tool/tool_node.py +++ b/api/core/workflow/nodes/tool/tool_node.py @@ -70,7 +70,13 @@ class ToolNode(BaseNode): try: from core.tools.tool_manager import ToolManager - variable_pool = self.graph_runtime_state.variable_pool if self._node_data.version != "1" else None + # This is an issue that caused problems before. + # Logically, we shouldn't use the node_data.version field for judgment + # But for backward compatibility with historical data + # this version field judgment is still preserved here. + variable_pool: VariablePool | None = None + if node_data.version != "1" or node_data.tool_node_version != "1": + variable_pool = self.graph_runtime_state.variable_pool tool_runtime = ToolManager.get_workflow_tool_runtime( self.tenant_id, self.app_id, self.node_id, self._node_data, self.invoke_from, variable_pool ) diff --git a/web/app/components/base/chat/chat/question.tsx b/web/app/components/base/chat/chat/question.tsx index 666a869a32..6630d9bb9d 100644 --- a/web/app/components/base/chat/chat/question.tsx +++ b/web/app/components/base/chat/chat/question.tsx @@ -117,7 +117,7 @@ const Question: FC = ({
{ diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index 4f68cfe87c..51955dc6c2 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -7,7 +7,7 @@ import { renderI18nObject } from '@/i18n' const nodeDefault: NodeDefault = { defaultValue: { - version: '2', + tool_node_version: '2', }, getAvailablePrevNodes(isChatMode) { return isChatMode @@ -62,27 +62,29 @@ const nodeDefault: NodeDefault = { const userSettings = toolValue.settings const reasoningConfig = toolValue.parameters const version = payload.version + const toolNodeVersion = payload.tool_node_version + const mergeVersion = version || toolNodeVersion schemas.forEach((schema: any) => { if (schema?.required) { - if (schema.form === 'form' && !version && !userSettings[schema.name]?.value) { + if (schema.form === 'form' && !mergeVersion && !userSettings[schema.name]?.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), } } - if (schema.form === 'form' && version && !userSettings[schema.name]?.value.value) { + if (schema.form === 'form' && mergeVersion && !userSettings[schema.name]?.value.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), } } - if (schema.form === 'llm' && !version && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value) { + if (schema.form === 'llm' && !mergeVersion && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), } } - if (schema.form === 'llm' && version && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value.value) { + if (schema.form === 'llm' && mergeVersion && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index 5a13a4a4f3..f163b3572a 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -12,6 +12,7 @@ export type AgentNodeType = CommonNodeType & { plugin_unique_identifier?: string memory?: Memory version?: string + tool_node_version?: string } export enum AgentFeature { diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index a8b80c0348..dd9236f24f 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -129,7 +129,7 @@ const useConfig = (id: string, payload: AgentNodeType) => { } const formattingLegacyData = () => { - if (inputs.version) + if (inputs.version || inputs.tool_node_version) return inputs const newData = produce(inputs, (draft) => { const schemas = currentStrategy?.parameters || [] @@ -140,7 +140,7 @@ const useConfig = (id: string, payload: AgentNodeType) => { if (targetSchema?.type === FormTypeEnum.multiToolSelector) draft.agent_parameters![key].value = draft.agent_parameters![key].value.map((tool: any) => formattingToolData(tool)) }) - draft.version = '2' + draft.tool_node_version = '2' }) return newData } diff --git a/web/app/components/workflow/nodes/tool/default.ts b/web/app/components/workflow/nodes/tool/default.ts index 1fdb9eed2d..1d4056be6d 100644 --- a/web/app/components/workflow/nodes/tool/default.ts +++ b/web/app/components/workflow/nodes/tool/default.ts @@ -10,7 +10,7 @@ const nodeDefault: NodeDefault = { defaultValue: { tool_parameters: {}, tool_configurations: {}, - version: '2', + tool_node_version: '2', }, getAvailablePrevNodes(isChatMode: boolean) { const nodes = isChatMode diff --git a/web/app/components/workflow/nodes/tool/types.ts b/web/app/components/workflow/nodes/tool/types.ts index 4584645a1e..6294b9b689 100644 --- a/web/app/components/workflow/nodes/tool/types.ts +++ b/web/app/components/workflow/nodes/tool/types.ts @@ -23,4 +23,5 @@ export type ToolNodeType = CommonNodeType & { output_schema: Record paramSchemas?: Record[] version?: string + tool_node_version?: string } diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index dc22d61ca5..92233f8d08 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -286,8 +286,8 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { } } - if (node.data.type === BlockEnum.Tool && !(node as Node).data.version) { - (node as Node).data.version = '2' + if (node.data.type === BlockEnum.Tool && !(node as Node).data.version && !(node as Node).data.tool_node_version) { + (node as Node).data.tool_node_version = '2' const toolConfigurations = (node as Node).data.tool_configurations if (toolConfigurations && Object.keys(toolConfigurations).length > 0) { From 74940ad3f29f8be2bb32c68ae713091041ef7b70 Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Mon, 21 Jul 2025 09:52:55 +0800 Subject: [PATCH 06/18] chore: code improvement for mcp_client and mcp_tools_manage_service (#22645) --- .../console/workspace/tool_providers.py | 2 +- api/core/mcp/auth/auth_provider.py | 2 +- api/core/mcp/mcp_client.py | 19 +++++++++++-------- api/core/tools/tool_manager.py | 2 +- ...service.py => mcp_tools_manage_service.py} | 17 +++++++---------- 5 files changed, 21 insertions(+), 21 deletions(-) rename api/services/tools/{mcp_tools_mange_service.py => mcp_tools_manage_service.py} (95%) diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index e41375e52b..c70bf84d2a 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -29,7 +29,7 @@ from libs.login import login_required from services.plugin.oauth_service import OAuthProxyService from services.tools.api_tools_manage_service import ApiToolManageService from services.tools.builtin_tools_manage_service import BuiltinToolManageService -from services.tools.mcp_tools_mange_service import MCPToolManageService +from services.tools.mcp_tools_manage_service import MCPToolManageService from services.tools.tool_labels_service import ToolLabelsService from services.tools.tools_manage_service import ToolCommonService from services.tools.tools_transform_service import ToolTransformService diff --git a/api/core/mcp/auth/auth_provider.py b/api/core/mcp/auth/auth_provider.py index cd55dbf64f..00d5a25956 100644 --- a/api/core/mcp/auth/auth_provider.py +++ b/api/core/mcp/auth/auth_provider.py @@ -8,7 +8,7 @@ from core.mcp.types import ( OAuthTokens, ) from models.tools import MCPToolProvider -from services.tools.mcp_tools_mange_service import MCPToolManageService +from services.tools.mcp_tools_manage_service import MCPToolManageService LATEST_PROTOCOL_VERSION = "1.0" diff --git a/api/core/mcp/mcp_client.py b/api/core/mcp/mcp_client.py index e9036de8c6..f7aa7bbd7b 100644 --- a/api/core/mcp/mcp_client.py +++ b/api/core/mcp/mcp_client.py @@ -68,15 +68,17 @@ class MCPClient: } parsed_url = urlparse(self.server_url) - path = parsed_url.path - method_name = path.rstrip("/").split("/")[-1] if path else "" - try: + path = parsed_url.path or "" + method_name = path.removesuffix("/").lower() + if method_name in connection_methods: client_factory = connection_methods[method_name] self.connect_server(client_factory, method_name) - except KeyError: + else: try: + logger.debug(f"Not supported method {method_name} found in URL path, trying default 'mcp' method.") self.connect_server(sse_client, "sse") except MCPConnectionError: + logger.debug("MCP connection failed with 'sse', falling back to 'mcp' method.") self.connect_server(streamablehttp_client, "mcp") def connect_server( @@ -91,7 +93,7 @@ class MCPClient: else {} ) self._streams_context = client_factory(url=self.server_url, headers=headers) - if self._streams_context is None: + if not self._streams_context: raise MCPConnectionError("Failed to create connection context") # Use exit_stack to manage context managers properly @@ -141,10 +143,11 @@ class MCPClient: try: # ExitStack will handle proper cleanup of all managed context managers self.exit_stack.close() + except Exception as e: + logging.exception("Error during cleanup") + raise ValueError(f"Error during cleanup: {e}") + finally: self._session = None self._session_context = None self._streams_context = None self._initialized = False - except Exception as e: - logging.exception("Error during cleanup") - raise ValueError(f"Error during cleanup: {e}") diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index d61856a8f5..7822bc389c 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -21,7 +21,7 @@ from core.tools.plugin_tool.tool import PluginTool from core.tools.utils.uuid_utils import is_valid_uuid from core.tools.workflow_as_tool.provider import WorkflowToolProviderController from core.workflow.entities.variable_pool import VariablePool -from services.tools.mcp_tools_mange_service import MCPToolManageService +from services.tools.mcp_tools_manage_service import MCPToolManageService if TYPE_CHECKING: from core.workflow.nodes.tool.entities import ToolEntity diff --git a/api/services/tools/mcp_tools_mange_service.py b/api/services/tools/mcp_tools_manage_service.py similarity index 95% rename from api/services/tools/mcp_tools_mange_service.py rename to api/services/tools/mcp_tools_manage_service.py index fda6da5983..e0e256912e 100644 --- a/api/services/tools/mcp_tools_mange_service.py +++ b/api/services/tools/mcp_tools_manage_service.py @@ -70,16 +70,15 @@ class MCPToolManageService: MCPToolProvider.server_url_hash == server_url_hash, MCPToolProvider.server_identifier == server_identifier, ), - MCPToolProvider.tenant_id == tenant_id, ) .first() ) if existing_provider: if existing_provider.name == name: raise ValueError(f"MCP tool {name} already exists") - elif existing_provider.server_url_hash == server_url_hash: + if existing_provider.server_url_hash == server_url_hash: raise ValueError(f"MCP tool {server_url} already exists") - elif existing_provider.server_identifier == server_identifier: + if existing_provider.server_identifier == server_identifier: raise ValueError(f"MCP tool {server_identifier} already exists") encrypted_server_url = encrypter.encrypt_token(tenant_id, server_url) mcp_tool = MCPToolProvider( @@ -111,15 +110,14 @@ class MCPToolManageService: ] @classmethod - def list_mcp_tool_from_remote_server(cls, tenant_id: str, provider_id: str): + def list_mcp_tool_from_remote_server(cls, tenant_id: str, provider_id: str) -> ToolProviderApiEntity: mcp_provider = cls.get_mcp_provider_by_provider_id(provider_id, tenant_id) - try: with MCPClient( mcp_provider.decrypted_server_url, provider_id, tenant_id, authed=mcp_provider.authed, for_list=True ) as mcp_client: tools = mcp_client.list_tools() - except MCPAuthError as e: + except MCPAuthError: raise ValueError("Please auth the tool first") except MCPError as e: raise ValueError(f"Failed to connect to MCP server: {e}") @@ -184,12 +182,11 @@ class MCPToolManageService: error_msg = str(e.orig) if "unique_mcp_provider_name" in error_msg: raise ValueError(f"MCP tool {name} already exists") - elif "unique_mcp_provider_server_url" in error_msg: + if "unique_mcp_provider_server_url" in error_msg: raise ValueError(f"MCP tool {server_url} already exists") - elif "unique_mcp_provider_server_identifier" in error_msg: + if "unique_mcp_provider_server_identifier" in error_msg: raise ValueError(f"MCP tool {server_identifier} already exists") - else: - raise + raise @classmethod def update_mcp_provider_credentials( From f8c7b28da7e03734bb759b1c336c9a70b277441b Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Mon, 21 Jul 2025 10:55:04 +0900 Subject: [PATCH 07/18] oxlint (#22584) --- web/eslint.config.mjs | 2 ++ web/package.json | 5 +++-- web/pnpm-lock.yaml | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index b276289ae8..8f1598e871 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -8,6 +8,7 @@ import storybook from 'eslint-plugin-storybook' import tailwind from 'eslint-plugin-tailwindcss' import reactHooks from 'eslint-plugin-react-hooks' import sonar from 'eslint-plugin-sonarjs' +import oxlint from 'eslint-plugin-oxlint' // import reactRefresh from 'eslint-plugin-react-refresh' @@ -245,4 +246,5 @@ export default combine( 'tailwindcss/migration-from-tailwind-2': 'warn', }, }, + oxlint.configs['flat/recommended'], ) diff --git a/web/package.json b/web/package.json index ee47c3692b..4bd21e6d86 100644 --- a/web/package.json +++ b/web/package.json @@ -21,8 +21,8 @@ "dev": "cross-env NODE_OPTIONS='--inspect' next dev", "build": "next build", "start": "cp -r .next/static .next/standalone/.next/static && cp -r public .next/standalone/public && cross-env PORT=$npm_config_port HOSTNAME=$npm_config_host node .next/standalone/server.js", - "lint": "pnpm eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache", - "lint-only-show-error": "pnpm eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet", + "lint": "pnpx oxlint && pnpm eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache", + "lint-only-show-error": "pnpx oxlint && pnpm eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet", "fix": "next lint --fix", "eslint-fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix", "eslint-fix-only-show-error": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix --quiet", @@ -198,6 +198,7 @@ "cross-env": "^7.0.3", "eslint": "^9.20.1", "eslint-config-next": "~15.3.5", + "eslint-plugin-oxlint": "^1.6.0", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-sonarjs": "^3.0.2", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index efc64a42c6..40825aec01 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -516,6 +516,9 @@ importers: eslint-config-next: specifier: ~15.3.5 version: 15.3.5(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + eslint-plugin-oxlint: + specifier: ^1.6.0 + version: 1.6.0 eslint-plugin-react-hooks: specifier: ^5.1.0 version: 5.2.0(eslint@9.31.0(jiti@1.21.7)) @@ -4775,6 +4778,9 @@ packages: resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} engines: {node: '>=5.0.0'} + eslint-plugin-oxlint@1.6.0: + resolution: {integrity: sha512-DH5p3sCf0nIAPscl3yGnBWXXraV0bdl66hpLxvfnabvg/GzpgXf+pOCWpGK3qDb0+AIUkh1R/7A8GkOXtlj0oA==} + eslint-plugin-perfectionist@4.15.0: resolution: {integrity: sha512-pC7PgoXyDnEXe14xvRUhBII8A3zRgggKqJFx2a82fjrItDs1BSI7zdZnQtM2yQvcyod6/ujmzb7ejKPx8lZTnw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -5832,6 +5838,9 @@ packages: resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -13169,6 +13178,10 @@ snapshots: eslint-plugin-no-only-tests@3.3.0: {} + eslint-plugin-oxlint@1.6.0: + dependencies: + jsonc-parser: 3.3.1 + eslint-plugin-perfectionist@4.15.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): dependencies: '@typescript-eslint/types': 8.37.0 @@ -14650,6 +14663,8 @@ snapshots: espree: 9.6.1 semver: 7.7.2 + jsonc-parser@3.3.1: {} + jsonfile@6.1.0: dependencies: universalify: 2.0.1 From 383a79772c225a66332fbcd3ee16858195dac088 Mon Sep 17 00:00:00 2001 From: Kushagra Singhal <74611061+kushagra21-afk@users.noreply.github.com> Date: Mon, 21 Jul 2025 07:28:10 +0530 Subject: [PATCH 08/18] Increased the character limitation (#22679) Co-authored-by: crazywoola <427733928@qq.com> --- ...5_07_21_0935-1a83934ad6d1_update_models.py | 51 +++++++++++++++++++ api/models/tools.py | 4 +- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 api/migrations/versions/2025_07_21_0935-1a83934ad6d1_update_models.py diff --git a/api/migrations/versions/2025_07_21_0935-1a83934ad6d1_update_models.py b/api/migrations/versions/2025_07_21_0935-1a83934ad6d1_update_models.py new file mode 100644 index 0000000000..3bdbafda7c --- /dev/null +++ b/api/migrations/versions/2025_07_21_0935-1a83934ad6d1_update_models.py @@ -0,0 +1,51 @@ +"""update models + +Revision ID: 1a83934ad6d1 +Revises: 71f5020c6470 +Create Date: 2025-07-21 09:35:48.774794 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1a83934ad6d1' +down_revision = '71f5020c6470' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('tool_mcp_providers', schema=None) as batch_op: + batch_op.alter_column('server_identifier', + existing_type=sa.VARCHAR(length=24), + type_=sa.String(length=64), + existing_nullable=False) + + with op.batch_alter_table('tool_model_invokes', schema=None) as batch_op: + batch_op.alter_column('tool_name', + existing_type=sa.VARCHAR(length=40), + type_=sa.String(length=128), + existing_nullable=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('tool_model_invokes', schema=None) as batch_op: + batch_op.alter_column('tool_name', + existing_type=sa.String(length=128), + type_=sa.VARCHAR(length=40), + existing_nullable=False) + + with op.batch_alter_table('tool_mcp_providers', schema=None) as batch_op: + batch_op.alter_column('server_identifier', + existing_type=sa.String(length=64), + type_=sa.VARCHAR(length=24), + existing_nullable=False) + + # ### end Alembic commands ### diff --git a/api/models/tools.py b/api/models/tools.py index 7c8b5853ba..f5fae8b796 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -254,7 +254,7 @@ class MCPToolProvider(Base): # name of the mcp provider name: Mapped[str] = mapped_column(db.String(40), nullable=False) # server identifier of the mcp provider - server_identifier: Mapped[str] = mapped_column(db.String(24), nullable=False) + server_identifier: Mapped[str] = mapped_column(db.String(64), nullable=False) # encrypted url of the mcp provider server_url: Mapped[str] = mapped_column(db.Text, nullable=False) # hash of server_url for uniqueness check @@ -358,7 +358,7 @@ class ToolModelInvoke(Base): # type tool_type = db.Column(db.String(40), nullable=False) # tool name - tool_name = db.Column(db.String(40), nullable=False) + tool_name = db.Column(db.String(128), nullable=False) # invoke parameters model_parameters = db.Column(db.Text, nullable=False) # prompt messages From cbc3474bbb795dec06ac05b28fc33c4a6ea4a192 Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:20:05 +0800 Subject: [PATCH 09/18] minor fix: fix dissolve tenant check permission always failed (#22292) --- api/services/account_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/services/account_service.py b/api/services/account_service.py index feabd43656..c13ae7a4f0 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -1070,8 +1070,8 @@ class TenantService: @staticmethod def dissolve_tenant(tenant: Tenant, operator: Account) -> None: """Dissolve tenant""" - if not TenantService.check_member_permission(tenant, operator, operator, "remove"): - raise NoPermissionError("No permission to dissolve tenant.") + TenantService.check_member_permission(tenant, operator, None, "remove") + db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id).delete() db.session.delete(tenant) db.session.commit() From d45e48eed7c5ecd2dfcd9c0a756d726a9111bb18 Mon Sep 17 00:00:00 2001 From: quicksand Date: Mon, 21 Jul 2025 11:22:32 +0800 Subject: [PATCH 10/18] fix: knowledge retrieval validation error (#22682) --- api/core/workflow/nodes/knowledge_retrieval/entities.py | 2 +- .../nodes/knowledge_retrieval/knowledge_retrieval_node.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/core/workflow/nodes/knowledge_retrieval/entities.py b/api/core/workflow/nodes/knowledge_retrieval/entities.py index e9122b1eec..f1767bdf9e 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/entities.py +++ b/api/core/workflow/nodes/knowledge_retrieval/entities.py @@ -118,7 +118,7 @@ class KnowledgeRetrievalNodeData(BaseNodeData): multiple_retrieval_config: Optional[MultipleRetrievalConfig] = None single_retrieval_config: Optional[SingleRetrievalConfig] = None metadata_filtering_mode: Optional[Literal["disabled", "automatic", "manual"]] = "disabled" - metadata_model_config: ModelConfig + metadata_model_config: Optional[ModelConfig] = None metadata_filtering_conditions: Optional[MetadataFilteringCondition] = None vision: VisionConfig = Field(default_factory=VisionConfig) 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 4e9a38f552..5f092dc2f1 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -509,6 +509,8 @@ class KnowledgeRetrievalNode(BaseNode): # get all metadata field metadata_fields = db.session.query(DatasetMetadata).filter(DatasetMetadata.dataset_id.in_(dataset_ids)).all() all_metadata_fields = [metadata_field.name for metadata_field in metadata_fields] + if node_data.metadata_model_config is None: + raise ValueError("metadata_model_config is required") # get metadata model instance and fetch model config model_instance, model_config = self.get_model_config(node_data.metadata_model_config) # fetch prompt messages @@ -701,7 +703,7 @@ class KnowledgeRetrievalNode(BaseNode): ) def _get_prompt_template(self, node_data: KnowledgeRetrievalNodeData, metadata_fields: list, query: str): - model_mode = ModelMode(node_data.metadata_model_config.mode) + model_mode = ModelMode(node_data.metadata_model_config.mode) # type: ignore input_text = query prompt_messages: list[LLMNodeChatModelMessage] = [] From bddeebd4c985ff0a45d47714b77bc518074d2614 Mon Sep 17 00:00:00 2001 From: Xin Zhang Date: Mon, 21 Jul 2025 12:40:47 +0800 Subject: [PATCH 11/18] refactor: remove unused dissolve_tenant static method (#22690) --- api/services/account_service.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/api/services/account_service.py b/api/services/account_service.py index c13ae7a4f0..352efb2f0c 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -1067,15 +1067,6 @@ class TenantService: target_member_join.role = new_role db.session.commit() - @staticmethod - def dissolve_tenant(tenant: Tenant, operator: Account) -> None: - """Dissolve tenant""" - TenantService.check_member_permission(tenant, operator, None, "remove") - - db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id).delete() - db.session.delete(tenant) - db.session.commit() - @staticmethod def get_custom_config(tenant_id: str) -> dict: tenant = db.get_or_404(Tenant, tenant_id) From 3b23fc5ad8fca32d06d0ad82cd6f3b21fa58c0a0 Mon Sep 17 00:00:00 2001 From: JianhengHou <36116720+JianhengHou@users.noreply.github.com> Date: Sun, 20 Jul 2025 22:55:16 -0700 Subject: [PATCH 12/18] fix: Correct and enhance the doc on CELERY_BROKER_URL in .env.example (#22693) Co-authored-by: Jianheng Hou --- docker/.env.example | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index ab98a40fef..6149f63165 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -283,11 +283,12 @@ REDIS_CLUSTERS_PASSWORD= # Celery Configuration # ------------------------------ -# Use redis as the broker, and redis db 1 for celery broker. -# Format as follows: `redis://:@:/` +# Use standalone redis as the broker, and redis db 1 for celery broker. (redis_username is usually set by defualt as empty) +# Format as follows: `redis://:@:/`. # Example: redis://:difyai123456@redis:6379/1 -# If use Redis Sentinel, format as follows: `sentinel://:@:/` -# Example: sentinel://localhost:26379/1;sentinel://localhost:26380/1;sentinel://localhost:26381/1 +# If use Redis Sentinel, format as follows: `sentinel://:@:/` +# For high availability, you can configure multiple Sentinel nodes (if provided) separated by semicolons like below example: +# Example: sentinel://:difyai123456@localhost:26379/1;sentinel://:difyai12345@localhost:26379/1;sentinel://:difyai12345@localhost:26379/1 CELERY_BROKER_URL=redis://:difyai123456@redis:6379/1 CELERY_BACKEND=redis BROKER_USE_SSL=false From 9251a66a10534615b7f381e47bd7d4e72057a642 Mon Sep 17 00:00:00 2001 From: 8bitpd <51897400+lpdink@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:03:37 +0800 Subject: [PATCH 13/18] fix: update analyticdb vector to do filter by metadata (#22698) Co-authored-by: xiaozeyu --- .../vdb/analyticdb/analyticdb_vector_openapi.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_openapi.py b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_openapi.py index 095752ea8e..6f3e15d166 100644 --- a/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_openapi.py +++ b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_openapi.py @@ -233,6 +233,12 @@ class AnalyticdbVectorOpenAPI: def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]: from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + document_ids_filter = kwargs.get("document_ids_filter") + where_clause = "" + if document_ids_filter: + document_ids = ", ".join(f"'{id}'" for id in document_ids_filter) + where_clause += f"metadata_->>'document_id' IN ({document_ids})" + score_threshold = kwargs.get("score_threshold") or 0.0 request = gpdb_20160503_models.QueryCollectionDataRequest( dbinstance_id=self.config.instance_id, @@ -245,7 +251,7 @@ class AnalyticdbVectorOpenAPI: vector=query_vector, content=None, top_k=kwargs.get("top_k", 4), - filter=None, + filter=where_clause, ) response = self._client.query_collection_data(request) documents = [] @@ -265,6 +271,11 @@ class AnalyticdbVectorOpenAPI: def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + document_ids_filter = kwargs.get("document_ids_filter") + where_clause = "" + if document_ids_filter: + document_ids = ", ".join(f"'{id}'" for id in document_ids_filter) + where_clause += f"metadata_->>'document_id' IN ({document_ids})" score_threshold = float(kwargs.get("score_threshold") or 0.0) request = gpdb_20160503_models.QueryCollectionDataRequest( dbinstance_id=self.config.instance_id, @@ -277,7 +288,7 @@ class AnalyticdbVectorOpenAPI: vector=None, content=query, top_k=kwargs.get("top_k", 4), - filter=None, + filter=where_clause, ) response = self._client.query_collection_data(request) documents = [] From 74981a65c6c70b4c552ffdceca7f5f81c17d7ad8 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:04:01 +0800 Subject: [PATCH 14/18] fix: Adjust tool selector popup styles (#22622) (#22697) --- .../tool-selector/index.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index 9c7c7a0c41..d2797b99f4 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -265,7 +265,7 @@ const ToolSelector: FC = ({ /> )} - +
<>
{t(`plugin.detailPanel.toolSelector.${isEdit ? 'toolSetting' : 'title'}`)}
@@ -309,15 +309,15 @@ const ToolSelector: FC = ({ {currentProvider && currentProvider.type === CollectionType.builtIn && currentProvider.allow_delete && ( <> -
- +
+
)} From c7382150b59e523b3fac5095f9cb89b124fecf29 Mon Sep 17 00:00:00 2001 From: Jason Young <44939412+farion1231@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:58:36 +0800 Subject: [PATCH 15/18] test: add comprehensive unit tests for Firecrawl and Watercrawl auth providers (#22705) --- .../services/auth/test_firecrawl_auth.py | 191 ++++++++++++++++ .../services/auth/test_watercrawl_auth.py | 205 ++++++++++++++++++ 2 files changed, 396 insertions(+) create mode 100644 api/tests/unit_tests/services/auth/test_firecrawl_auth.py create mode 100644 api/tests/unit_tests/services/auth/test_watercrawl_auth.py diff --git a/api/tests/unit_tests/services/auth/test_firecrawl_auth.py b/api/tests/unit_tests/services/auth/test_firecrawl_auth.py new file mode 100644 index 0000000000..ffdf5897ed --- /dev/null +++ b/api/tests/unit_tests/services/auth/test_firecrawl_auth.py @@ -0,0 +1,191 @@ +from unittest.mock import MagicMock, patch + +import pytest +import requests + +from services.auth.firecrawl.firecrawl import FirecrawlAuth + + +class TestFirecrawlAuth: + @pytest.fixture + def valid_credentials(self): + """Fixture for valid bearer credentials""" + return {"auth_type": "bearer", "config": {"api_key": "test_api_key_123"}} + + @pytest.fixture + def auth_instance(self, valid_credentials): + """Fixture for FirecrawlAuth instance with valid credentials""" + return FirecrawlAuth(valid_credentials) + + def test_should_initialize_with_valid_bearer_credentials(self, valid_credentials): + """Test successful initialization with valid bearer credentials""" + auth = FirecrawlAuth(valid_credentials) + assert auth.api_key == "test_api_key_123" + assert auth.base_url == "https://api.firecrawl.dev" + assert auth.credentials == valid_credentials + + def test_should_initialize_with_custom_base_url(self): + """Test initialization with custom base URL""" + credentials = { + "auth_type": "bearer", + "config": {"api_key": "test_api_key_123", "base_url": "https://custom.firecrawl.dev"}, + } + auth = FirecrawlAuth(credentials) + assert auth.api_key == "test_api_key_123" + assert auth.base_url == "https://custom.firecrawl.dev" + + @pytest.mark.parametrize( + ("auth_type", "expected_error"), + [ + ("basic", "Invalid auth type, Firecrawl auth type must be Bearer"), + ("x-api-key", "Invalid auth type, Firecrawl auth type must be Bearer"), + ("", "Invalid auth type, Firecrawl auth type must be Bearer"), + ], + ) + def test_should_raise_error_for_invalid_auth_type(self, auth_type, expected_error): + """Test that non-bearer auth types raise ValueError""" + credentials = {"auth_type": auth_type, "config": {"api_key": "test_api_key_123"}} + with pytest.raises(ValueError) as exc_info: + FirecrawlAuth(credentials) + assert str(exc_info.value) == expected_error + + @pytest.mark.parametrize( + ("credentials", "expected_error"), + [ + ({"auth_type": "bearer", "config": {}}, "No API key provided"), + ({"auth_type": "bearer"}, "No API key provided"), + ({"auth_type": "bearer", "config": {"api_key": ""}}, "No API key provided"), + ({"auth_type": "bearer", "config": {"api_key": None}}, "No API key provided"), + ], + ) + def test_should_raise_error_for_missing_api_key(self, credentials, expected_error): + """Test that missing or empty API key raises ValueError""" + with pytest.raises(ValueError) as exc_info: + FirecrawlAuth(credentials) + assert str(exc_info.value) == expected_error + + @patch("services.auth.firecrawl.firecrawl.requests.post") + def test_should_validate_valid_credentials_successfully(self, mock_post, auth_instance): + """Test successful credential validation""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_post.return_value = mock_response + + result = auth_instance.validate_credentials() + + assert result is True + expected_data = { + "url": "https://example.com", + "includePaths": [], + "excludePaths": [], + "limit": 1, + "scrapeOptions": {"onlyMainContent": True}, + } + mock_post.assert_called_once_with( + "https://api.firecrawl.dev/v1/crawl", + headers={"Content-Type": "application/json", "Authorization": "Bearer test_api_key_123"}, + json=expected_data, + ) + + @pytest.mark.parametrize( + ("status_code", "error_message"), + [ + (402, "Payment required"), + (409, "Conflict error"), + (500, "Internal server error"), + ], + ) + @patch("services.auth.firecrawl.firecrawl.requests.post") + def test_should_handle_http_errors(self, mock_post, status_code, error_message, auth_instance): + """Test handling of various HTTP error codes""" + mock_response = MagicMock() + mock_response.status_code = status_code + mock_response.json.return_value = {"error": error_message} + mock_post.return_value = mock_response + + with pytest.raises(Exception) as exc_info: + auth_instance.validate_credentials() + assert str(exc_info.value) == f"Failed to authorize. Status code: {status_code}. Error: {error_message}" + + @pytest.mark.parametrize( + ("status_code", "response_text", "has_json_error", "expected_error_contains"), + [ + (403, '{"error": "Forbidden"}', True, "Failed to authorize. Status code: 403. Error: Forbidden"), + (404, "", True, "Unexpected error occurred while trying to authorize. Status code: 404"), + (401, "Not JSON", True, "Expecting value"), # JSON decode error + ], + ) + @patch("services.auth.firecrawl.firecrawl.requests.post") + def test_should_handle_unexpected_errors( + self, mock_post, status_code, response_text, has_json_error, expected_error_contains, auth_instance + ): + """Test handling of unexpected errors with various response formats""" + mock_response = MagicMock() + mock_response.status_code = status_code + mock_response.text = response_text + if has_json_error: + mock_response.json.side_effect = Exception("Not JSON") + mock_post.return_value = mock_response + + with pytest.raises(Exception) as exc_info: + auth_instance.validate_credentials() + assert expected_error_contains in str(exc_info.value) + + @pytest.mark.parametrize( + ("exception_type", "exception_message"), + [ + (requests.ConnectionError, "Network error"), + (requests.Timeout, "Request timeout"), + (requests.ReadTimeout, "Read timeout"), + (requests.ConnectTimeout, "Connection timeout"), + ], + ) + @patch("services.auth.firecrawl.firecrawl.requests.post") + def test_should_handle_network_errors(self, mock_post, exception_type, exception_message, auth_instance): + """Test handling of various network-related errors including timeouts""" + mock_post.side_effect = exception_type(exception_message) + + with pytest.raises(exception_type) as exc_info: + auth_instance.validate_credentials() + assert exception_message in str(exc_info.value) + + def test_should_not_expose_api_key_in_error_messages(self): + """Test that API key is not exposed in error messages""" + credentials = {"auth_type": "bearer", "config": {"api_key": "super_secret_key_12345"}} + auth = FirecrawlAuth(credentials) + + # Verify API key is stored but not in any error message + assert auth.api_key == "super_secret_key_12345" + + # Test various error scenarios don't expose the key + with pytest.raises(ValueError) as exc_info: + FirecrawlAuth({"auth_type": "basic", "config": {"api_key": "super_secret_key_12345"}}) + assert "super_secret_key_12345" not in str(exc_info.value) + + @patch("services.auth.firecrawl.firecrawl.requests.post") + def test_should_use_custom_base_url_in_validation(self, mock_post): + """Test that custom base URL is used in validation""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_post.return_value = mock_response + + credentials = { + "auth_type": "bearer", + "config": {"api_key": "test_api_key_123", "base_url": "https://custom.firecrawl.dev"}, + } + auth = FirecrawlAuth(credentials) + result = auth.validate_credentials() + + assert result is True + assert mock_post.call_args[0][0] == "https://custom.firecrawl.dev/v1/crawl" + + @patch("services.auth.firecrawl.firecrawl.requests.post") + def test_should_handle_timeout_with_retry_suggestion(self, mock_post, auth_instance): + """Test that timeout errors are handled gracefully with appropriate error message""" + mock_post.side_effect = requests.Timeout("The request timed out after 30 seconds") + + with pytest.raises(requests.Timeout) as exc_info: + auth_instance.validate_credentials() + + # Verify the timeout exception is raised with original message + assert "timed out" in str(exc_info.value) diff --git a/api/tests/unit_tests/services/auth/test_watercrawl_auth.py b/api/tests/unit_tests/services/auth/test_watercrawl_auth.py new file mode 100644 index 0000000000..bacf0b24ea --- /dev/null +++ b/api/tests/unit_tests/services/auth/test_watercrawl_auth.py @@ -0,0 +1,205 @@ +from unittest.mock import MagicMock, patch + +import pytest +import requests + +from services.auth.watercrawl.watercrawl import WatercrawlAuth + + +class TestWatercrawlAuth: + @pytest.fixture + def valid_credentials(self): + """Fixture for valid x-api-key credentials""" + return {"auth_type": "x-api-key", "config": {"api_key": "test_api_key_123"}} + + @pytest.fixture + def auth_instance(self, valid_credentials): + """Fixture for WatercrawlAuth instance with valid credentials""" + return WatercrawlAuth(valid_credentials) + + def test_should_initialize_with_valid_x_api_key_credentials(self, valid_credentials): + """Test successful initialization with valid x-api-key credentials""" + auth = WatercrawlAuth(valid_credentials) + assert auth.api_key == "test_api_key_123" + assert auth.base_url == "https://app.watercrawl.dev" + assert auth.credentials == valid_credentials + + def test_should_initialize_with_custom_base_url(self): + """Test initialization with custom base URL""" + credentials = { + "auth_type": "x-api-key", + "config": {"api_key": "test_api_key_123", "base_url": "https://custom.watercrawl.dev"}, + } + auth = WatercrawlAuth(credentials) + assert auth.api_key == "test_api_key_123" + assert auth.base_url == "https://custom.watercrawl.dev" + + @pytest.mark.parametrize( + ("auth_type", "expected_error"), + [ + ("bearer", "Invalid auth type, WaterCrawl auth type must be x-api-key"), + ("basic", "Invalid auth type, WaterCrawl auth type must be x-api-key"), + ("", "Invalid auth type, WaterCrawl auth type must be x-api-key"), + ], + ) + def test_should_raise_error_for_invalid_auth_type(self, auth_type, expected_error): + """Test that non-x-api-key auth types raise ValueError""" + credentials = {"auth_type": auth_type, "config": {"api_key": "test_api_key_123"}} + with pytest.raises(ValueError) as exc_info: + WatercrawlAuth(credentials) + assert str(exc_info.value) == expected_error + + @pytest.mark.parametrize( + ("credentials", "expected_error"), + [ + ({"auth_type": "x-api-key", "config": {}}, "No API key provided"), + ({"auth_type": "x-api-key"}, "No API key provided"), + ({"auth_type": "x-api-key", "config": {"api_key": ""}}, "No API key provided"), + ({"auth_type": "x-api-key", "config": {"api_key": None}}, "No API key provided"), + ], + ) + def test_should_raise_error_for_missing_api_key(self, credentials, expected_error): + """Test that missing or empty API key raises ValueError""" + with pytest.raises(ValueError) as exc_info: + WatercrawlAuth(credentials) + assert str(exc_info.value) == expected_error + + @patch("services.auth.watercrawl.watercrawl.requests.get") + def test_should_validate_valid_credentials_successfully(self, mock_get, auth_instance): + """Test successful credential validation""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_get.return_value = mock_response + + result = auth_instance.validate_credentials() + + assert result is True + mock_get.assert_called_once_with( + "https://app.watercrawl.dev/api/v1/core/crawl-requests/", + headers={"Content-Type": "application/json", "X-API-KEY": "test_api_key_123"}, + ) + + @pytest.mark.parametrize( + ("status_code", "error_message"), + [ + (402, "Payment required"), + (409, "Conflict error"), + (500, "Internal server error"), + ], + ) + @patch("services.auth.watercrawl.watercrawl.requests.get") + def test_should_handle_http_errors(self, mock_get, status_code, error_message, auth_instance): + """Test handling of various HTTP error codes""" + mock_response = MagicMock() + mock_response.status_code = status_code + mock_response.json.return_value = {"error": error_message} + mock_get.return_value = mock_response + + with pytest.raises(Exception) as exc_info: + auth_instance.validate_credentials() + assert str(exc_info.value) == f"Failed to authorize. Status code: {status_code}. Error: {error_message}" + + @pytest.mark.parametrize( + ("status_code", "response_text", "has_json_error", "expected_error_contains"), + [ + (403, '{"error": "Forbidden"}', True, "Failed to authorize. Status code: 403. Error: Forbidden"), + (404, "", True, "Unexpected error occurred while trying to authorize. Status code: 404"), + (401, "Not JSON", True, "Expecting value"), # JSON decode error + ], + ) + @patch("services.auth.watercrawl.watercrawl.requests.get") + def test_should_handle_unexpected_errors( + self, mock_get, status_code, response_text, has_json_error, expected_error_contains, auth_instance + ): + """Test handling of unexpected errors with various response formats""" + mock_response = MagicMock() + mock_response.status_code = status_code + mock_response.text = response_text + if has_json_error: + mock_response.json.side_effect = Exception("Not JSON") + mock_get.return_value = mock_response + + with pytest.raises(Exception) as exc_info: + auth_instance.validate_credentials() + assert expected_error_contains in str(exc_info.value) + + @pytest.mark.parametrize( + ("exception_type", "exception_message"), + [ + (requests.ConnectionError, "Network error"), + (requests.Timeout, "Request timeout"), + (requests.ReadTimeout, "Read timeout"), + (requests.ConnectTimeout, "Connection timeout"), + ], + ) + @patch("services.auth.watercrawl.watercrawl.requests.get") + def test_should_handle_network_errors(self, mock_get, exception_type, exception_message, auth_instance): + """Test handling of various network-related errors including timeouts""" + mock_get.side_effect = exception_type(exception_message) + + with pytest.raises(exception_type) as exc_info: + auth_instance.validate_credentials() + assert exception_message in str(exc_info.value) + + def test_should_not_expose_api_key_in_error_messages(self): + """Test that API key is not exposed in error messages""" + credentials = {"auth_type": "x-api-key", "config": {"api_key": "super_secret_key_12345"}} + auth = WatercrawlAuth(credentials) + + # Verify API key is stored but not in any error message + assert auth.api_key == "super_secret_key_12345" + + # Test various error scenarios don't expose the key + with pytest.raises(ValueError) as exc_info: + WatercrawlAuth({"auth_type": "bearer", "config": {"api_key": "super_secret_key_12345"}}) + assert "super_secret_key_12345" not in str(exc_info.value) + + @patch("services.auth.watercrawl.watercrawl.requests.get") + def test_should_use_custom_base_url_in_validation(self, mock_get): + """Test that custom base URL is used in validation""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_get.return_value = mock_response + + credentials = { + "auth_type": "x-api-key", + "config": {"api_key": "test_api_key_123", "base_url": "https://custom.watercrawl.dev"}, + } + auth = WatercrawlAuth(credentials) + result = auth.validate_credentials() + + assert result is True + assert mock_get.call_args[0][0] == "https://custom.watercrawl.dev/api/v1/core/crawl-requests/" + + @pytest.mark.parametrize( + ("base_url", "expected_url"), + [ + ("https://app.watercrawl.dev", "https://app.watercrawl.dev/api/v1/core/crawl-requests/"), + ("https://app.watercrawl.dev/", "https://app.watercrawl.dev/api/v1/core/crawl-requests/"), + ("https://app.watercrawl.dev//", "https://app.watercrawl.dev/api/v1/core/crawl-requests/"), + ], + ) + @patch("services.auth.watercrawl.watercrawl.requests.get") + def test_should_use_urljoin_for_url_construction(self, mock_get, base_url, expected_url): + """Test that urljoin is used correctly for URL construction with various base URLs""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_get.return_value = mock_response + + credentials = {"auth_type": "x-api-key", "config": {"api_key": "test_api_key_123", "base_url": base_url}} + auth = WatercrawlAuth(credentials) + auth.validate_credentials() + + # Verify the correct URL was called + assert mock_get.call_args[0][0] == expected_url + + @patch("services.auth.watercrawl.watercrawl.requests.get") + def test_should_handle_timeout_with_retry_suggestion(self, mock_get, auth_instance): + """Test that timeout errors are handled gracefully with appropriate error message""" + mock_get.side_effect = requests.Timeout("The request timed out after 30 seconds") + + with pytest.raises(requests.Timeout) as exc_info: + auth_instance.validate_credentials() + + # Verify the timeout exception is raised with original message + assert "timed out" in str(exc_info.value) From ab012fe1a2faef3a703323599c11a4bc960f7f46 Mon Sep 17 00:00:00 2001 From: uply23333 Date: Mon, 21 Jul 2025 15:59:37 +0800 Subject: [PATCH 16/18] fix: improve document filtering in full text search(elasticsearch) (#22683) --- .../vdb/elasticsearch/elasticsearch_vector.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py b/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py index 44cc5d3e98..ad39717183 100644 --- a/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py +++ b/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py @@ -147,10 +147,17 @@ class ElasticSearchVector(BaseVector): return docs def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: - query_str = {"match": {Field.CONTENT_KEY.value: query}} + query_str: dict[str, Any] = {"match": {Field.CONTENT_KEY.value: query}} document_ids_filter = kwargs.get("document_ids_filter") + if document_ids_filter: - query_str["filter"] = {"terms": {"metadata.document_id": document_ids_filter}} # type: ignore + query_str = { + "bool": { + "must": {"match": {Field.CONTENT_KEY.value: query}}, + "filter": {"terms": {"metadata.document_id": document_ids_filter}}, + } + } + results = self._client.search(index=self._collection_name, query=query_str, size=kwargs.get("top_k", 4)) docs = [] for hit in results["hits"]["hits"]: From a83e4ed9a41ed373361d0668ea2d9b4e0ac494d6 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Mon, 21 Jul 2025 16:35:52 +0800 Subject: [PATCH 17/18] Perf: remove user profile loading (#22710) --- web/app/(commonLayout)/layout.tsx | 6 +- web/app/account/account-page/index.tsx | 9 ++- web/app/account/layout.tsx | 2 +- web/app/components/app-sidebar/app-info.tsx | 16 ++--- .../components/app/create-app-modal/index.tsx | 8 +-- .../app/overview/embedded/index.tsx | 4 +- web/app/components/apps/app-card.tsx | 21 ++----- web/app/components/base/avatar/index.tsx | 2 +- .../billing/apps-full-in-dialog/index.tsx | 4 +- ...ser-initor.tsx => browser-initializer.tsx} | 6 +- .../components/header/account-about/index.tsx | 14 ++--- .../header/account-dropdown/index.tsx | 8 +-- .../header/account-dropdown/support.tsx | 4 +- web/app/components/header/env-nav/index.tsx | 10 ++-- .../steps/install.tsx | 9 ++- .../steps/install.tsx | 9 ++- .../components/plugins/plugin-item/index.tsx | 8 +-- ...ntry-initor.tsx => sentry-initializer.tsx} | 6 +- .../{swr-initor.tsx => swr-initializer.tsx} | 8 +-- web/app/layout.tsx | 18 +++--- web/context/app-context.tsx | 58 +++++++------------ web/context/query-client.tsx | 2 +- web/service/common.ts | 2 +- 23 files changed, 100 insertions(+), 134 deletions(-) rename web/app/components/{browser-initor.tsx => browser-initializer.tsx} (88%) rename web/app/components/{sentry-initor.tsx => sentry-initializer.tsx} (85%) rename web/app/components/{swr-initor.tsx => swr-initializer.tsx} (95%) diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index d07e2a99d9..64186a1b10 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -1,6 +1,6 @@ import React from 'react' import type { ReactNode } from 'react' -import SwrInitor from '@/app/components/swr-initor' +import SwrInitializer from '@/app/components/swr-initializer' import { AppContextProvider } from '@/context/app-context' import GA, { GaType } from '@/app/components/base/ga' import HeaderWrapper from '@/app/components/header/header-wrapper' @@ -13,7 +13,7 @@ const Layout = ({ children }: { children: ReactNode }) => { return ( <> - + @@ -26,7 +26,7 @@ const Layout = ({ children }: { children: ReactNode }) => { - + ) } diff --git a/web/app/account/account-page/index.tsx b/web/app/account/account-page/index.tsx index 55fa2983dd..47b8f045d2 100644 --- a/web/app/account/account-page/index.tsx +++ b/web/app/account/account-page/index.tsx @@ -1,5 +1,6 @@ 'use client' import { useState } from 'react' +import useSWR from 'swr' import { useTranslation } from 'react-i18next' import { RiGraduationCapFill, @@ -22,6 +23,8 @@ import PremiumBadge from '@/app/components/base/premium-badge' import { useGlobalPublicStore } from '@/context/global-public-context' import EmailChangeModal from './email-change-modal' import { validPassword } from '@/config' +import { fetchAppList } from '@/service/apps' +import type { App } from '@/types/app' const titleClassName = ` system-sm-semibold text-text-secondary @@ -33,7 +36,9 @@ const descriptionClassName = ` export default function AccountPage() { const { t } = useTranslation() const { systemFeatures } = useGlobalPublicStore() - const { mutateUserProfile, userProfile, apps } = useAppContext() + const { data: appList } = useSWR({ url: '/apps', params: { page: 1, limit: 100, name: '' } }, fetchAppList) + const apps = appList?.data || [] + const { mutateUserProfile, userProfile } = useAppContext() const { isEducationAccount } = useProviderContext() const { notify } = useContext(ToastContext) const [editNameModalVisible, setEditNameModalVisible] = useState(false) @@ -202,7 +207,7 @@ export default function AccountPage() { {!!apps.length && ( ({ ...app, key: app.id, name: app.name }))} + items={apps.map((app: App) => ({ ...app, key: app.id, name: app.name }))} renderItem={renderAppItem} wrapperClassName='mt-2' /> diff --git a/web/app/account/layout.tsx b/web/app/account/layout.tsx index e74716fb3b..b3225b5341 100644 --- a/web/app/account/layout.tsx +++ b/web/app/account/layout.tsx @@ -1,7 +1,7 @@ import React from 'react' import type { ReactNode } from 'react' import Header from './header' -import SwrInitor from '@/app/components/swr-initor' +import SwrInitor from '@/app/components/swr-initializer' import { AppContextProvider } from '@/context/app-context' import GA, { GaType } from '@/app/components/base/ga' import HeaderWrapper from '@/app/components/header/header-wrapper' diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index e85eaa2f53..c35047bbc5 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' -import { useContext, useContextSelector } from 'use-context-selector' +import { useContext } from 'use-context-selector' import React, { useCallback, useState } from 'react' import { RiDeleteBinLine, @@ -15,7 +15,7 @@ import AppIcon from '../base/app-icon' import cn from '@/utils/classnames' import { useStore as useAppStore } from '@/app/components/app/store' import { ToastContext } from '@/app/components/base/toast' -import AppsContext, { useAppContext } from '@/context/app-context' +import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' @@ -73,11 +73,6 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx const [showImportDSLModal, setShowImportDSLModal] = useState(false) const [secretEnvList, setSecretEnvList] = useState([]) - const mutateApps = useContextSelector( - AppsContext, - state => state.mutateApps, - ) - const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ name, icon_type, @@ -106,12 +101,11 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx message: t('app.editDone'), }) setAppDetail(app) - mutateApps() } catch { notify({ type: 'error', message: t('app.editFailed') }) } - }, [appDetail, mutateApps, notify, setAppDetail, t]) + }, [appDetail, notify, setAppDetail, t]) const onCopy: DuplicateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background }) => { if (!appDetail) @@ -131,7 +125,6 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx message: t('app.newApp.appCreated'), }) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') - mutateApps() onPlanInfoChanged() getRedirection(true, newApp, replace) } @@ -186,7 +179,6 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx try { await deleteApp(appDetail.id) notify({ type: 'success', message: t('app.appDeleted') }) - mutateApps() onPlanInfoChanged() setAppDetail() replace('/apps') @@ -198,7 +190,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx }) } setShowConfirmDelete(false) - }, [appDetail, mutateApps, notify, onPlanInfoChanged, replace, setAppDetail, t]) + }, [appDetail, notify, onPlanInfoChanged, replace, setAppDetail, t]) const { isCurrentWorkspaceEditor } = useAppContext() diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index f0a0da41a5..bdc839e848 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -4,7 +4,7 @@ import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' -import { useContext, useContextSelector } from 'use-context-selector' +import { useContext } from 'use-context-selector' import { RiArrowRightLine, RiArrowRightSLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react' import Link from 'next/link' import { useDebounceFn, useKeyPress } from 'ahooks' @@ -15,7 +15,7 @@ import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' import cn from '@/utils/classnames' import { basePath } from '@/utils/var' -import AppsContext, { useAppContext } from '@/context/app-context' +import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { ToastContext } from '@/app/components/base/toast' import type { AppMode } from '@/types/app' @@ -41,7 +41,6 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) const { t } = useTranslation() const { push } = useRouter() const { notify } = useContext(ToastContext) - const mutateApps = useContextSelector(AppsContext, state => state.mutateApps) const [appMode, setAppMode] = useState('advanced-chat') const [appIcon, setAppIcon] = useState({ type: 'emoji', icon: '🤖', background: '#FFEAD5' }) @@ -80,7 +79,6 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) notify({ type: 'success', message: t('app.newApp.appCreated') }) onSuccess() onClose() - mutateApps() localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') getRedirection(isCurrentWorkspaceEditor, app, push) } @@ -88,7 +86,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) } isCreatingRef.current = false - }, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceEditor]) + }, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor]) const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 }) useKeyPress(['meta.enter', 'ctrl.enter'], () => { diff --git a/web/app/components/app/overview/embedded/index.tsx b/web/app/components/app/overview/embedded/index.tsx index b48eac5458..9d97eae38d 100644 --- a/web/app/components/app/overview/embedded/index.tsx +++ b/web/app/components/app/overview/embedded/index.tsx @@ -90,10 +90,10 @@ const Embedded = ({ siteInfo, isShow, onClose, appBaseUrl, accessToken, classNam const [option, setOption] = useState
) diff --git a/web/app/components/billing/apps-full-in-dialog/index.tsx b/web/app/components/billing/apps-full-in-dialog/index.tsx index b721b94b01..fda3213713 100644 --- a/web/app/components/billing/apps-full-in-dialog/index.tsx +++ b/web/app/components/billing/apps-full-in-dialog/index.tsx @@ -21,7 +21,7 @@ const AppsFull: FC<{ loc: string; className?: string; }> = ({ }) => { const { t } = useTranslation() const { plan } = useProviderContext() - const { userProfile, langeniusVersionInfo } = useAppContext() + const { userProfile, langGeniusVersionInfo } = useAppContext() const isTeam = plan.type === Plan.team const usage = plan.usage.buildApps const total = plan.total.buildApps @@ -62,7 +62,7 @@ const AppsFull: FC<{ loc: string; className?: string; }> = ({ )} {plan.type !== Plan.sandbox && plan.type !== Plan.professional && ( diff --git a/web/app/components/browser-initor.tsx b/web/app/components/browser-initializer.tsx similarity index 88% rename from web/app/components/browser-initor.tsx rename to web/app/components/browser-initializer.tsx index f2f4b02dc0..fcae22c448 100644 --- a/web/app/components/browser-initor.tsx +++ b/web/app/components/browser-initializer.tsx @@ -43,10 +43,10 @@ Object.defineProperty(globalThis, 'sessionStorage', { value: sessionStorage, }) -const BrowserInitor = ({ +const BrowserInitializer = ({ children, -}: { children: React.ReactNode }) => { +}: { children: React.ReactElement }) => { return children } -export default BrowserInitor +export default BrowserInitializer diff --git a/web/app/components/header/account-about/index.tsx b/web/app/components/header/account-about/index.tsx index 280e276be9..2eb8cdf82f 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -12,16 +12,16 @@ import { noop } from 'lodash-es' import { useGlobalPublicStore } from '@/context/global-public-context' type IAccountSettingProps = { - langeniusVersionInfo: LangGeniusVersionResponse + langGeniusVersionInfo: LangGeniusVersionResponse onCancel: () => void } export default function AccountAbout({ - langeniusVersionInfo, + langGeniusVersionInfo, onCancel, }: IAccountSettingProps) { const { t } = useTranslation() - const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version + const isLatest = langGeniusVersionInfo.current_version === langGeniusVersionInfo.latest_version const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return ( @@ -43,7 +43,7 @@ export default function AccountAbout({ /> : } -
Version {langeniusVersionInfo?.current_version}
+
Version {langGeniusVersionInfo?.current_version}
© {dayjs().year()} LangGenius, Inc., Contributors.
@@ -63,8 +63,8 @@ export default function AccountAbout({
{ isLatest - ? t('common.about.latestAvailable', { version: langeniusVersionInfo.latest_version }) - : t('common.about.nowAvailable', { version: langeniusVersionInfo.latest_version }) + ? t('common.about.latestAvailable', { version: langGeniusVersionInfo.latest_version }) + : t('common.about.nowAvailable', { version: langGeniusVersionInfo.latest_version }) }
@@ -80,7 +80,7 @@ export default function AccountAbout({ !isLatest && !IS_CE_EDITION && (
@@ -217,7 +217,7 @@ export default function AppSelector() { } { - aboutVisible && setAboutVisible(false)} langeniusVersionInfo={langeniusVersionInfo} /> + aboutVisible && setAboutVisible(false)} langGeniusVersionInfo={langGeniusVersionInfo} /> }
) diff --git a/web/app/components/header/account-dropdown/support.tsx b/web/app/components/header/account-dropdown/support.tsx index e4731d2b6e..6435bcaeb4 100644 --- a/web/app/components/header/account-dropdown/support.tsx +++ b/web/app/components/header/account-dropdown/support.tsx @@ -16,7 +16,7 @@ export default function Support() { ` const { t } = useTranslation() const { plan } = useProviderContext() - const { userProfile, langeniusVersionInfo } = useAppContext() + const { userProfile, langGeniusVersionInfo } = useAppContext() const canEmailSupport = plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise return @@ -53,7 +53,7 @@ export default function Support() { className={cn(itemClassName, 'group justify-between', 'data-[active]:bg-state-base-hover', )} - href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)} + href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo.current_version)} target='_blank' rel='noopener noreferrer'>
{t('common.userProfile.emailSupport')}
diff --git a/web/app/components/header/env-nav/index.tsx b/web/app/components/header/env-nav/index.tsx index 3f0b0f01dd..e7535c69f0 100644 --- a/web/app/components/header/env-nav/index.tsx +++ b/web/app/components/header/env-nav/index.tsx @@ -12,8 +12,8 @@ const headerEnvClassName: { [k: string]: string } = { const EnvNav = () => { const { t } = useTranslation() - const { langeniusVersionInfo } = useAppContext() - const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT' + const { langGeniusVersionInfo } = useAppContext() + const showEnvTag = langGeniusVersionInfo.current_env === 'TESTING' || langGeniusVersionInfo.current_env === 'DEVELOPMENT' if (!showEnvTag) return null @@ -21,10 +21,10 @@ const EnvNav = () => { return (
{ - langeniusVersionInfo.current_env === 'TESTING' && ( + langGeniusVersionInfo.current_env === 'TESTING' && ( <>
{t('common.environment.testing')}
@@ -32,7 +32,7 @@ const EnvNav = () => { ) } { - langeniusVersionInfo.current_env === 'DEVELOPMENT' && ( + langGeniusVersionInfo.current_env === 'DEVELOPMENT' && ( <>
{t('common.environment.development')}
diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx index 1ddc52ced9..ff4bb8de90 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx @@ -48,7 +48,6 @@ const Installed: FC = ({ useEffect(() => { if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) onInstalled() - // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasInstalled]) const [isInstalling, setIsInstalling] = React.useState(false) @@ -105,12 +104,12 @@ const Installed: FC = ({ } } - const { langeniusVersionInfo } = useAppContext() + const { langGeniusVersionInfo } = useAppContext() const isDifyVersionCompatible = useMemo(() => { - if (!langeniusVersionInfo.current_version) + if (!langGeniusVersionInfo.current_version) return true - return gte(langeniusVersionInfo.current_version, payload.meta.minimum_dify_version ?? '0.0.0') - }, [langeniusVersionInfo.current_version, payload.meta.minimum_dify_version]) + return gte(langGeniusVersionInfo.current_version, payload.meta.minimum_dify_version ?? '0.0.0') + }, [langGeniusVersionInfo.current_version, payload.meta.minimum_dify_version]) return ( <> diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx index dbc7c97d88..3bbf8c9a39 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx @@ -59,7 +59,6 @@ const Installed: FC = ({ useEffect(() => { if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) onInstalled() - // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasInstalled]) const handleCancel = () => { @@ -120,12 +119,12 @@ const Installed: FC = ({ } } - const { langeniusVersionInfo } = useAppContext() + const { langGeniusVersionInfo } = useAppContext() const { data: pluginDeclaration } = usePluginDeclarationFromMarketPlace(uniqueIdentifier) const isDifyVersionCompatible = useMemo(() => { - if (!pluginDeclaration || !langeniusVersionInfo.current_version) return true - return gte(langeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0') - }, [langeniusVersionInfo.current_version, pluginDeclaration]) + if (!pluginDeclaration || !langGeniusVersionInfo.current_version) return true + return gte(langGeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0') + }, [langGeniusVersionInfo.current_version, pluginDeclaration]) const { canInstall } = useInstallPluginLimit({ ...payload, from: 'marketplace' }) return ( diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 058f1783f2..0ea8538692 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -62,13 +62,13 @@ const PluginItem: FC = ({ return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : '' }, [source, author]) - const { langeniusVersionInfo } = useAppContext() + const { langGeniusVersionInfo } = useAppContext() const isDifyVersionCompatible = useMemo(() => { - if (!langeniusVersionInfo.current_version) + if (!langGeniusVersionInfo.current_version) return true - return gte(langeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0') - }, [declarationMeta.minimum_dify_version, langeniusVersionInfo.current_version]) + return gte(langGeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0') + }, [declarationMeta.minimum_dify_version, langGeniusVersionInfo.current_version]) const handleDelete = () => { refreshPluginList({ category } as any) diff --git a/web/app/components/sentry-initor.tsx b/web/app/components/sentry-initializer.tsx similarity index 85% rename from web/app/components/sentry-initor.tsx rename to web/app/components/sentry-initializer.tsx index 457a1cf7c7..10c056f21b 100644 --- a/web/app/components/sentry-initor.tsx +++ b/web/app/components/sentry-initializer.tsx @@ -5,9 +5,9 @@ import * as Sentry from '@sentry/react' const isDevelopment = process.env.NODE_ENV === 'development' -const SentryInit = ({ +const SentryInitializer = ({ children, -}: { children: React.ReactNode }) => { +}: { children: React.ReactElement }) => { useEffect(() => { const SENTRY_DSN = document?.body?.getAttribute('data-public-sentry-dsn') if (!isDevelopment && SENTRY_DSN) { @@ -26,4 +26,4 @@ const SentryInit = ({ return children } -export default SentryInit +export default SentryInitializer diff --git a/web/app/components/swr-initor.tsx b/web/app/components/swr-initializer.tsx similarity index 95% rename from web/app/components/swr-initor.tsx rename to web/app/components/swr-initializer.tsx index 8f9c5b4e05..3592a0e017 100644 --- a/web/app/components/swr-initor.tsx +++ b/web/app/components/swr-initializer.tsx @@ -10,12 +10,12 @@ import { EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION, } from '@/app/education-apply/constants' -type SwrInitorProps = { +type SwrInitializerProps = { children: ReactNode } -const SwrInitor = ({ +const SwrInitializer = ({ children, -}: SwrInitorProps) => { +}: SwrInitializerProps) => { const router = useRouter() const searchParams = useSearchParams() const consoleToken = decodeURIComponent(searchParams.get('access_token') || '') @@ -86,4 +86,4 @@ const SwrInitor = ({ : null } -export default SwrInitor +export default SwrInitializer diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 525445db30..f086499ca4 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,10 +1,10 @@ import RoutePrefixHandle from './routePrefixHandle' import type { Viewport } from 'next' import I18nServer from './components/i18n-server' -import BrowserInitor from './components/browser-initor' -import SentryInitor from './components/sentry-initor' +import BrowserInitializer from './components/browser-initializer' +import SentryInitializer from './components/sentry-initializer' import { getLocaleOnServer } from '@/i18n/server' -import { TanstackQueryIniter } from '@/context/query-client' +import { TanstackQueryInitializer } from '@/context/query-client' import { ThemeProvider } from 'next-themes' import './styles/globals.css' import './styles/markdown.scss' @@ -62,9 +62,9 @@ const LocaleLayout = async ({ className="color-scheme h-full select-auto" {...datasetMap} > - - - + + + - - - + + + diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index 9b95b0f1eb..f941cb43b4 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -1,20 +1,15 @@ 'use client' -import { createRef, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import useSWR from 'swr' import { createContext, useContext, useContextSelector } from 'use-context-selector' import type { FC, ReactNode } from 'react' -import { fetchAppList } from '@/service/apps' -import Loading from '@/app/components/base/loading' -import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile } from '@/service/common' -import type { App } from '@/types/app' +import { fetchCurrentWorkspace, fetchLangGeniusVersion, fetchUserProfile } from '@/service/common' import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import MaintenanceNotice from '@/app/components/header/maintenance-notice' import { noop } from 'lodash-es' export type AppContextValue = { - apps: App[] - mutateApps: VoidFunction userProfile: UserProfileResponse mutateUserProfile: VoidFunction currentWorkspace: ICurrentWorkspace @@ -23,13 +18,21 @@ export type AppContextValue = { isCurrentWorkspaceEditor: boolean isCurrentWorkspaceDatasetOperator: boolean mutateCurrentWorkspace: VoidFunction - pageContainerRef: React.RefObject - langeniusVersionInfo: LangGeniusVersionResponse + langGeniusVersionInfo: LangGeniusVersionResponse useSelector: typeof useSelector isLoadingCurrentWorkspace: boolean } -const initialLangeniusVersionInfo = { +const userProfilePlaceholder = { + id: '', + name: '', + email: '', + avatar: '', + avatar_url: '', + is_password_set: false, + } + +const initialLangGeniusVersionInfo = { current_env: '', current_version: '', latest_version: '', @@ -50,16 +53,7 @@ const initialWorkspaceInfo: ICurrentWorkspace = { } const AppContext = createContext({ - apps: [], - mutateApps: noop, - userProfile: { - id: '', - name: '', - email: '', - avatar: '', - avatar_url: '', - is_password_set: false, - }, + userProfile: userProfilePlaceholder, currentWorkspace: initialWorkspaceInfo, isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, @@ -67,8 +61,7 @@ const AppContext = createContext({ isCurrentWorkspaceDatasetOperator: false, mutateUserProfile: noop, mutateCurrentWorkspace: noop, - pageContainerRef: createRef(), - langeniusVersionInfo: initialLangeniusVersionInfo, + langGeniusVersionInfo: initialLangGeniusVersionInfo, useSelector, isLoadingCurrentWorkspace: false, }) @@ -82,14 +75,11 @@ export type AppContextProviderProps = { } export const AppContextProvider: FC = ({ children }) => { - const pageContainerRef = useRef(null) - - const { data: appList, mutate: mutateApps } = useSWR({ url: '/apps', params: { page: 1, limit: 30, name: '' } }, fetchAppList) const { data: userProfileResponse, mutate: mutateUserProfile } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile) const { data: currentWorkspaceResponse, mutate: mutateCurrentWorkspace, isLoading: isLoadingCurrentWorkspace } = useSWR({ url: '/workspaces/current', params: {} }, fetchCurrentWorkspace) - const [userProfile, setUserProfile] = useState() - const [langeniusVersionInfo, setLangeniusVersionInfo] = useState(initialLangeniusVersionInfo) + const [userProfile, setUserProfile] = useState(userProfilePlaceholder) + const [langGeniusVersionInfo, setLangGeniusVersionInfo] = useState(initialLangGeniusVersionInfo) const [currentWorkspace, setCurrentWorkspace] = useState(initialWorkspaceInfo) const isCurrentWorkspaceManager = useMemo(() => ['owner', 'admin'].includes(currentWorkspace.role), [currentWorkspace.role]) const isCurrentWorkspaceOwner = useMemo(() => currentWorkspace.role === 'owner', [currentWorkspace.role]) @@ -101,8 +91,8 @@ export const AppContextProvider: FC = ({ children }) => setUserProfile(result) const current_version = userProfileResponse.headers.get('x-version') const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env') - const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } }) - setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env }) + const versionData = await fetchLangGeniusVersion({ url: '/version', params: { current_version } }) + setLangGeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env }) } }, [userProfileResponse]) @@ -115,17 +105,11 @@ export const AppContextProvider: FC = ({ children }) => setCurrentWorkspace(currentWorkspaceResponse) }, [currentWorkspaceResponse]) - if (!appList || !userProfile) - return - return ( = ({ children }) => }}>
{globalThis.document?.body?.getAttribute('data-public-maintenance-notice') && } -
+
{children}
diff --git a/web/context/query-client.tsx b/web/context/query-client.tsx index f85930515c..3deccba439 100644 --- a/web/context/query-client.tsx +++ b/web/context/query-client.tsx @@ -14,7 +14,7 @@ const client = new QueryClient({ }, }) -export const TanstackQueryIniter: FC = (props) => { +export const TanstackQueryInitializer: FC = (props) => { const { children } = props return {children} diff --git a/web/service/common.ts b/web/service/common.ts index 99eb58e2a0..d70315f5c6 100644 --- a/web/service/common.ts +++ b/web/service/common.ts @@ -88,7 +88,7 @@ export const logout: Fetcher(url, params) } -export const fetchLanggeniusVersion: Fetcher }> = ({ url, params }) => { +export const fetchLangGeniusVersion: Fetcher }> = ({ url, params }) => { return get(url, { params }) } From 8fa3b3f9315f8cf87fc0ea5de4dff6c0fde10a96 Mon Sep 17 00:00:00 2001 From: Nite Knite Date: Mon, 21 Jul 2025 16:36:12 +0800 Subject: [PATCH 18/18] fix: prevent app type description from overflowing the card (#22711) --- web/app/components/app/create-app-modal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index bdc839e848..c37f7b051a 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -296,7 +296,7 @@ function AppTypeCard({ icon, title, description, active, onClick }: AppTypeCardP > {icon}
{title}
-
{description}
+
{description}
}