Merge branch 'main' into fix/issue-35910

This commit is contained in:
FFXN 2026-05-08 14:02:27 +08:00 committed by GitHub
commit 18e2ecd6c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 68 additions and 47 deletions

View File

@ -876,10 +876,10 @@ class ToolBuiltinProviderSetDefaultApi(Resource):
@login_required @login_required
@account_initialization_required @account_initialization_required
def post(self, provider): def post(self, provider):
current_user, current_tenant_id = current_account_with_tenant() _, current_tenant_id = current_account_with_tenant()
payload = BuiltinProviderDefaultCredentialPayload.model_validate(console_ns.payload or {}) payload = BuiltinProviderDefaultCredentialPayload.model_validate(console_ns.payload or {})
return BuiltinToolManageService.set_default_provider( return BuiltinToolManageService.set_default_provider(
tenant_id=current_tenant_id, user_id=current_user.id, provider=provider, id=payload.id tenant_id=current_tenant_id, provider=provider, id=payload.id
) )

View File

@ -408,7 +408,7 @@ class BuiltinToolManageService:
return {"result": "success"} return {"result": "success"}
@staticmethod @staticmethod
def set_default_provider(tenant_id: str, user_id: str, provider: str, id: str): def set_default_provider(tenant_id: str, provider: str, id: str):
""" """
set default provider set default provider
""" """
@ -422,12 +422,11 @@ class BuiltinToolManageService:
if target_provider is None: if target_provider is None:
raise ValueError("provider not found") raise ValueError("provider not found")
# clear default provider # clear default provider (tenant-scoped: only one default per provider per workspace)
session.execute( session.execute(
update(BuiltinToolProvider) update(BuiltinToolProvider)
.where( .where(
BuiltinToolProvider.tenant_id == tenant_id, BuiltinToolProvider.tenant_id == tenant_id,
BuiltinToolProvider.user_id == user_id,
BuiltinToolProvider.provider == provider, BuiltinToolProvider.provider == provider,
BuiltinToolProvider.is_default.is_(True), BuiltinToolProvider.is_default.is_(True),
) )

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import base64 import base64
import json import json
from types import SimpleNamespace from types import SimpleNamespace
from typing import Any, cast from typing import Any
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from uuid import uuid4 from uuid import uuid4
@ -71,6 +71,7 @@ def _pending_yaml_content(version: str = "99.0.0") -> bytes:
def _app_stub(**overrides: Any) -> App: def _app_stub(**overrides: Any) -> App:
"""Create a stub App object for testing without hitting the database."""
defaults = { defaults = {
"id": str(uuid4()), "id": str(uuid4()),
"tenant_id": _DEFAULT_TENANT_ID, "tenant_id": _DEFAULT_TENANT_ID,
@ -83,7 +84,10 @@ def _app_stub(**overrides: Any) -> App:
"use_icon_as_answer_icon": False, "use_icon_as_answer_icon": False,
"app_model_config": None, "app_model_config": None,
} }
return cast(App, SimpleNamespace(**(defaults | overrides))) app = MagicMock(spec=App)
for key, value in (defaults | overrides).items():
object.__setattr__(app, key, value)
return app
class TestAppDslService: class TestAppDslService:

View File

@ -180,7 +180,7 @@ class TestSetDefaultProvider:
session.scalar.return_value = None session.scalar.return_value = None
with pytest.raises(ValueError, match="provider not found"): with pytest.raises(ValueError, match="provider not found"):
BuiltinToolManageService.set_default_provider("t", "u", "p", "id") BuiltinToolManageService.set_default_provider("t", "p", "id")
@patch(f"{MODULE}.sessionmaker") @patch(f"{MODULE}.sessionmaker")
@patch(f"{MODULE}.db") @patch(f"{MODULE}.db")
@ -189,11 +189,29 @@ class TestSetDefaultProvider:
target = MagicMock() target = MagicMock()
session.scalar.return_value = target session.scalar.return_value = target
result = BuiltinToolManageService.set_default_provider("t", "u", "p", "id") result = BuiltinToolManageService.set_default_provider("t", "p", "id")
assert result == {"result": "success"} assert result == {"result": "success"}
assert target.is_default is True assert target.is_default is True
@patch(f"{MODULE}.sessionmaker")
@patch(f"{MODULE}.db")
def test_clear_default_is_tenant_scoped_not_user_scoped(self, mock_db, mock_sm_cls):
# Regression: clearing prior defaults must NOT filter by user_id, otherwise
# two workspace members can each leave their own credential as default at
# the same time (the default flag is tenant-scoped, not per-user).
session = _mock_sessionmaker(mock_sm_cls)
session.scalar.return_value = MagicMock()
BuiltinToolManageService.set_default_provider("tenant-1", "google", "cred-id")
session.execute.assert_called_once()
update_stmt = session.execute.call_args.args[0]
compiled = str(update_stmt.compile(compile_kwargs={"literal_binds": True}))
assert "user_id" not in compiled
assert "tenant_id" in compiled
assert "provider" in compiled
class TestUpdateBuiltinToolProvider: class TestUpdateBuiltinToolProvider:
@patch(f"{MODULE}.sessionmaker") @patch(f"{MODULE}.sessionmaker")

View File

@ -261,14 +261,14 @@
"notSetAPIKey.trailFinished": "Deneme süresi sona erdi", "notSetAPIKey.trailFinished": "Deneme süresi sona erdi",
"notSetVar": "Değişkenler, kullanıcıların form doldururken prompt kelimelerini veya açılış ifadelerini getirmesine izin verir. Prompt kelimelerine \"{{input}}\" yazmayı deneyebilirsiniz.", "notSetVar": "Değişkenler, kullanıcıların form doldururken prompt kelimelerini veya açılış ifadelerini getirmesine izin verir. Prompt kelimelerine \"{{input}}\" yazmayı deneyebilirsiniz.",
"openingStatement.add": "Ekle", "openingStatement.add": "Ekle",
"openingStatement.editorTitle": "Acilis Mesaji", "openingStatement.editorTitle": "Açılış Mesajı",
"openingStatement.noDataPlaceHolder": "Kullanıcı ile konuşmayı başlatmak, AI'ın konuşma uygulamalarında onlarla daha yakın bir bağlantı kurmasına yardımcı olabilir.", "openingStatement.noDataPlaceHolder": "Kullanıcı ile konuşmayı başlatmak, AI'ın konuşma uygulamalarında onlarla daha yakın bir bağlantı kurmasına yardımcı olabilir.",
"openingStatement.notIncludeKey": "Başlangıç promptu değişkeni içermiyor: {{key}}. Lütfen bunu başlangıç promptuna ekleyin.", "openingStatement.notIncludeKey": "Başlangıç promptu değişkeni içermiyor: {{key}}. Lütfen bunu başlangıç promptuna ekleyin.",
"openingStatement.openingQuestion": "Açılış Soruları", "openingStatement.openingQuestion": "Açılış Soruları",
"openingStatement.openingQuestionDescription": "Acilis mesajindan sonra gosterilen istege bagli yonlendirmeler; kullanicilarin konusmayi surdurmesine yardimci olur.", "openingStatement.openingQuestionDescription": "Açılış mesajından sonra gösterilen isteğe bağlı yönlendirmeler; kullanıcıların konuşmayı sürdürmesine yardımcı olur.",
"openingStatement.openingQuestionPlaceholder": "Bir acilis sorusu girin.", "openingStatement.openingQuestionPlaceholder": "Bir açılış sorusu girin.",
"openingStatement.placeholderLine1": "Buradan baslayin. Yapay zekanin gondermesi gereken ilk mesaji yazin.", "openingStatement.placeholderLine1": "Buradan başlayın. Yapay zekânın göndermesi gereken ilk mesajı yazın.",
"openingStatement.placeholderLine2": "Değişkenler kullanabilirsiniz, {{variable}} yazmayı deneyin.", "openingStatement.placeholderLine2": "Değişkenleri kullanabilirsiniz; {{variable}} yazmayı deneyin.",
"openingStatement.title": "Konuşma Başlatıcı", "openingStatement.title": "Konuşma Başlatıcı",
"openingStatement.tooShort": "Konuşma için açılış ifadeleri oluşturmak için en az 20 kelimelik başlangıç promptu gereklidir.", "openingStatement.tooShort": "Konuşma için açılış ifadeleri oluşturmak için en az 20 kelimelik başlangıç promptu gereklidir.",
"openingStatement.varTip": "Değişkenler kullanabilirsiniz, örneğin {{variable}} yazmayı deneyin", "openingStatement.varTip": "Değişkenler kullanabilirsiniz, örneğin {{variable}} yazmayı deneyin",

View File

@ -136,11 +136,11 @@
"marketplace.template.fetchFailed": "Şablon alınamadı", "marketplace.template.fetchFailed": "Şablon alınamadı",
"marketplace.template.importConfirm": "İçe Aktar", "marketplace.template.importConfirm": "İçe Aktar",
"marketplace.template.importFailed": "Şablon içe aktarılamadı", "marketplace.template.importFailed": "Şablon içe aktarılamadı",
"marketplace.template.modalTitle": "Marketplace'den İçe Aktar", "marketplace.template.modalTitle": "Pazar Yeri'nden İçe Aktar",
"marketplace.template.overview": "Genel Bakış", "marketplace.template.overview": "Genel Bakış",
"marketplace.template.publishedBy": "Yayıncı", "marketplace.template.publishedBy": "Yayıncı",
"marketplace.template.usageCount": "Kullanım", "marketplace.template.usageCount": "Kullanım",
"marketplace.template.viewOnMarketplace": "Marketplace'de Görüntüle", "marketplace.template.viewOnMarketplace": "Pazar Yeri'nde Görüntüle",
"maxActiveRequests": "Maksimum eş zamanlı istekler", "maxActiveRequests": "Maksimum eş zamanlı istekler",
"maxActiveRequestsPlaceholder": "Sınırsız için 0 girin", "maxActiveRequestsPlaceholder": "Sınırsız için 0 girin",
"maxActiveRequestsTip": "Her uygulama için maksimum eşzamanlı aktif istek sayısı (sınırsız için 0)", "maxActiveRequestsTip": "Her uygulama için maksimum eşzamanlı aktif istek sayısı (sınırsız için 0)",

View File

@ -1,24 +1,24 @@
{ {
"applied.activeSubscription.description": "Aktif bir aboneliğiniz var. Aboneliğinizin süresi dolduktan sonra eğitim indirimini kullanabilirsiniz. Aboneliğinizi <stripeLink>Stripe</stripeLink>'da onaylayın.", "applied.activeSubscription.description": "Aktif bir aboneliğiniz var. Aboneliğinizin süresi dolduktan sonra eğitim indirimini kullanabilirsiniz. Aboneliğinizi <stripeLink>Stripe</stripeLink>'da onaylayın.",
"applied.description": "Tebrikler! Eğitim indirimi için başarıyla başvurdunuz.", "applied.description": "Tebrikler! Eğitim indirimi için başarıyla başvurdunuz.",
"applied.noPaymentPermission.description": "Bu workspace'te ödeme izniniz yok. Eğitim indirimini kullanmak için lütfen faturalamayı yönetebileceğiniz bir workspace'e geçin.", "applied.noPaymentPermission.description": "Bu çalışma alanında ödeme izniniz yok. Eğitim indirimini kullanmak için lütfen faturalamayı yönetebileceğiniz bir çalışma alanına geçin.",
"applied.noPaymentPermission.returnHome": "Dify'e geri dön", "applied.noPaymentPermission.returnHome": "Dify'e geri dön",
"applied.step1.description": "Eğitim indirimi için başarıyla başvurdunuz.", "applied.step1.description": "Eğitim indirimi için başarıyla başvurdunuz.",
"applied.step1.title": "Adım 1", "applied.step1.title": "Adım 1",
"applied.step2.description": "Eğitim indirimiyle kullanmak istediğiniz workspace'i seçin.", "applied.step2.description": "Eğitim indirimiyle kullanmak istediğiniz çalışma alanını seçin.",
"applied.step2.title": "Adım 2", "applied.step2.title": "Adım 2",
"applied.tabs.activeSubscription": "Abonelikte", "applied.tabs.activeSubscription": "Abonelikte",
"applied.tabs.eligible": "Satın alabilir", "applied.tabs.eligible": "Satın alabilir",
"applied.tabs.noPaymentPermission": "Ödeme izni yok", "applied.tabs.noPaymentPermission": "Ödeme izni yok",
"applied.title": "Eğitim indirimi uygulandı", "applied.title": "Eğitim indirimi uygulandı",
"applied.workspace.plan": "Ücretli plan", "applied.workspace.plan": "Ücretli plan",
"applied.workspace.title": "Mevcut Workspace", "applied.workspace.title": "Mevcut Çalışma Alanı",
"currentSigned": "ŞU ANDA GİRİŞ YAPILDIĞI KİŞİ", "currentSigned": "ŞU ANDA GİRİŞ YAPILDIĞI KİŞİ",
"educationPricingConfirm.billingPeriod.monthly": "aylık", "educationPricingConfirm.billingPeriod.monthly": "aylık",
"educationPricingConfirm.billingPeriod.yearly": "yıllık", "educationPricingConfirm.billingPeriod.yearly": "yıllık",
"educationPricingConfirm.cancel": "İptal", "educationPricingConfirm.cancel": "İptal",
"educationPricingConfirm.continue": "İndirim olmadan devam et", "educationPricingConfirm.continue": "İndirim olmadan devam et",
"educationPricingConfirm.description": "{{planName}} {{billingPeriod}} planınız eğitim indirimini desteklemiyor. Yalnızca Professional yıllık plan uygun.", "educationPricingConfirm.description": "{{planName}} {{billingPeriod}} planınız eğitim indirimini desteklemiyor. Yalnızca yıllık Professional planı uygundur.",
"educationPricingConfirm.title": "Eğitim indirimi mevcut değil", "educationPricingConfirm.title": "Eğitim indirimi mevcut değil",
"emailLabel": "Şu anki e-posta adresin", "emailLabel": "Şu anki e-posta adresin",
"form.schoolName.placeholder": "Okulunuzun resmi, kısaltılmamış adını girin", "form.schoolName.placeholder": "Okulunuzun resmi, kısaltılmamış adını girin",

View File

@ -16,8 +16,8 @@
"chat.privacyPolicyMiddle": "gizlilik politikası", "chat.privacyPolicyMiddle": "gizlilik politikası",
"chat.privacyPolicyRight": "uygulama geliştiricisi tarafından sağlanmıştır.", "chat.privacyPolicyRight": "uygulama geliştiricisi tarafından sağlanmıştır.",
"chat.privatePromptConfigTitle": "Konuşma ayarları", "chat.privatePromptConfigTitle": "Konuşma ayarları",
"chat.prompt": "Prompt", "chat.prompt": "İstem",
"chat.publicPromptConfigTitle": "Başlangıç Promptu", "chat.publicPromptConfigTitle": "Başlangıç İstemi",
"chat.resetChat": "Konuşmayı sıfırla", "chat.resetChat": "Konuşmayı sıfırla",
"chat.startChat": "Sohbete Başla", "chat.startChat": "Sohbete Başla",
"chat.temporarySystemIssue": "Üzgünüz, geçici sistem sorunu.", "chat.temporarySystemIssue": "Üzgünüz, geçici sistem sorunu.",

View File

@ -111,33 +111,33 @@
"chatVariable.updatedAt": "Güncellenme zamanı: ", "chatVariable.updatedAt": "Güncellenme zamanı: ",
"collaboration.historyAction.generic": "Bir işbirlikçi geri alma/yeniden yapma gerçekleştirdi", "collaboration.historyAction.generic": "Bir işbirlikçi geri alma/yeniden yapma gerçekleştirdi",
"comments.actions.addComment": "Yorum ekle", "comments.actions.addComment": "Yorum ekle",
"comments.actions.deleteReply": "Delete reply", "comments.actions.deleteReply": "Yanıtı sil",
"comments.actions.editComment": "Yorumu düzenle", "comments.actions.editComment": "Yorumu düzenle",
"comments.actions.editReply": "Edit reply", "comments.actions.editReply": "Yanıtı düzenle",
"comments.aria.closeComment": "Close comment", "comments.aria.closeComment": "Yorumu kapat",
"comments.aria.commentActions": "Yorum işlemleri", "comments.aria.commentActions": "Yorum işlemleri",
"comments.aria.deleteComment": "Delete thread", "comments.aria.deleteComment": "Konuyu sil",
"comments.aria.filterComments": "Yorumları filtrele", "comments.aria.filterComments": "Yorumları filtrele",
"comments.aria.nextComment": "Next comment", "comments.aria.nextComment": "Sonraki yorum",
"comments.aria.previousComment": "Previous comment", "comments.aria.previousComment": "Önceki yorum",
"comments.aria.replyActions": "Reply actions", "comments.aria.replyActions": "Yanıt işlemleri",
"comments.aria.resolveComment": "Resolve", "comments.aria.resolveComment": "Çöz",
"comments.confirm.deleteReplyDesc": "This reply will be removed permanently.", "comments.confirm.deleteReplyDesc": "Bu yanıt kalıcı olarak kaldırılacak.",
"comments.confirm.deleteReplyTitle": "Delete this reply?", "comments.confirm.deleteReplyTitle": "Bu yanıt silinsin mi?",
"comments.confirm.deleteThreadDesc": "This action will permanently delete the thread and all its replies. This cannot be undone.", "comments.confirm.deleteThreadDesc": "Bu işlem konuyu ve tüm yanıtlarını kalıcı olarak siler. Bu işlem geri alınamaz.",
"comments.confirm.deleteThreadTitle": "Delete this thread?", "comments.confirm.deleteThreadTitle": "Bu konu silinsin mi?",
"comments.fallback.user": "User", "comments.fallback.user": "Kullanıcı",
"comments.filter.all": "Tümü", "comments.filter.all": "Tümü",
"comments.filter.onlyYourThreads": "Yalnızca senin başlıkların", "comments.filter.onlyYourThreads": "Yalnızca kendi konuların",
"comments.filter.showResolved": "Çözülenleri göster", "comments.filter.showResolved": "Çözülenleri göster",
"comments.loading": "Loading…", "comments.loading": "Yükleniyor…",
"comments.noComments": "No comments yet", "comments.noComments": "Henüz yorum yok",
"comments.panelTitle": "Comment", "comments.panelTitle": "Yorum",
"comments.placeholder.add": "Add a comment", "comments.placeholder.add": "Yorum ekle",
"comments.placeholder.editComment": "Yorumu düzenle", "comments.placeholder.editComment": "Yorumu düzenle",
"comments.placeholder.editReply": "Edit reply", "comments.placeholder.editReply": "Yanıtı düzenle",
"comments.placeholder.reply": "Reply", "comments.placeholder.reply": "Yanıtla",
"comments.reply": "Reply", "comments.reply": "Yanıtla",
"common.ImageUploadLegacyTip": "Artık başlangıç formunda dosya türü değişkenleri oluşturabilirsiniz. Gelecekte resim yükleme özelliğini artık desteklemeyeceğiz.", "common.ImageUploadLegacyTip": "Artık başlangıç formunda dosya türü değişkenleri oluşturabilirsiniz. Gelecekte resim yükleme özelliğini artık desteklemeyeceğiz.",
"common.accessAPIReference": "API Referansına Eriş", "common.accessAPIReference": "API Referansına Eriş",
"common.addBlock": "Düğüm Ekle", "common.addBlock": "Düğüm Ekle",
@ -229,8 +229,8 @@
"common.previewPlaceholder": "Sohbet Robotunu hata ayıklamak için aşağıdaki kutuya içerik girin", "common.previewPlaceholder": "Sohbet Robotunu hata ayıklamak için aşağıdaki kutuya içerik girin",
"common.processData": "Veriyi İşle", "common.processData": "Veriyi İşle",
"common.publish": "Yayınla", "common.publish": "Yayınla",
"common.publishToMarketplace": "Marketplace'de Yayınla", "common.publishToMarketplace": "Pazar Yeri'nde Yayınla",
"common.publishToMarketplaceFailed": "Marketplace'de Yayınlama Başarısız", "common.publishToMarketplaceFailed": "Pazar Yeri'nde yayınlanamadı",
"common.publishUpdate": "Güncellemeyi Yayınla", "common.publishUpdate": "Güncellemeyi Yayınla",
"common.published": "Yayınlandı", "common.published": "Yayınlandı",
"common.publishedAt": "Yayınlandı", "common.publishedAt": "Yayınlandı",
@ -812,7 +812,7 @@
"nodes.llm.outputVars.output": "İçerik Üret", "nodes.llm.outputVars.output": "İçerik Üret",
"nodes.llm.outputVars.reasoning_content": "Akıl yürütme içeriği", "nodes.llm.outputVars.reasoning_content": "Akıl yürütme içeriği",
"nodes.llm.outputVars.usage": "Model Kullanım Bilgileri", "nodes.llm.outputVars.usage": "Model Kullanım Bilgileri",
"nodes.llm.prompt": "prompt", "nodes.llm.prompt": "istem",
"nodes.llm.reasoningFormat.separated": "Ayrı düşünce etiketleri", "nodes.llm.reasoningFormat.separated": "Ayrı düşünce etiketleri",
"nodes.llm.reasoningFormat.tagged": "Etiketleri düşünmeye devam et", "nodes.llm.reasoningFormat.tagged": "Etiketleri düşünmeye devam et",
"nodes.llm.reasoningFormat.title": "Akıl yürütme etiket ayrımını etkinleştir", "nodes.llm.reasoningFormat.title": "Akıl yürütme etiket ayrımını etkinleştir",
@ -891,7 +891,7 @@
"nodes.parameterExtractor.outputVars.usage": "Model Kullanım Bilgileri", "nodes.parameterExtractor.outputVars.usage": "Model Kullanım Bilgileri",
"nodes.parameterExtractor.reasoningMode": "Akıl Yürütme Modu", "nodes.parameterExtractor.reasoningMode": "Akıl Yürütme Modu",
"nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Fonksiyon/Araç Çağrısı", "nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Fonksiyon/Araç Çağrısı",
"nodes.parameterExtractor.reasoningModePrompt": "Prompt", "nodes.parameterExtractor.reasoningModePrompt": "İstem",
"nodes.parameterExtractor.reasoningModeTip": "Modelin fonksiyon çağırma veya istemler için talimatlara yanıt verme yeteneğine bağlı olarak uygun akıl yürütme modunu seçebilirsiniz.", "nodes.parameterExtractor.reasoningModeTip": "Modelin fonksiyon çağırma veya istemler için talimatlara yanıt verme yeteneğine bağlı olarak uygun akıl yürütme modunu seçebilirsiniz.",
"nodes.questionClassifiers.addClass": "Sınıf Ekle", "nodes.questionClassifiers.addClass": "Sınıf Ekle",
"nodes.questionClassifiers.advancedSetting": "Gelişmiş Ayarlar", "nodes.questionClassifiers.advancedSetting": "Gelişmiş Ayarlar",