From 72adb5468c352bce77c1b2289ea55faa2f95a7c4 Mon Sep 17 00:00:00 2001 From: Renzo <170978465+RenzoMXD@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:46:30 -0500 Subject: [PATCH 01/11] refactor: migrate session.query to select API in retrieval_service (#34638) --- api/core/rag/datasource/retrieval_service.py | 30 +++++----- .../datasource/test_datasource_retrieval.py | 58 +++++++++---------- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/api/core/rag/datasource/retrieval_service.py b/api/core/rag/datasource/retrieval_service.py index fcbc3ffbfa..e90d6a3694 100644 --- a/api/core/rag/datasource/retrieval_service.py +++ b/api/core/rag/datasource/retrieval_service.py @@ -240,7 +240,7 @@ class RetrievalService: @classmethod def _get_dataset(cls, dataset_id: str) -> Dataset | None: with Session(db.engine) as session: - return session.query(Dataset).where(Dataset.id == dataset_id).first() + return session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1)) @classmethod def keyword_search( @@ -573,15 +573,13 @@ class RetrievalService: # Batch query summaries for segments retrieved via summary (only enabled summaries) if summary_segment_ids: - summaries = ( - session.query(DocumentSegmentSummary) - .filter( + summaries = session.scalars( + select(DocumentSegmentSummary).where( DocumentSegmentSummary.chunk_id.in_(list(summary_segment_ids)), DocumentSegmentSummary.status == "completed", - DocumentSegmentSummary.enabled == True, # Only retrieve enabled summaries + DocumentSegmentSummary.enabled.is_(True), # Only retrieve enabled summaries ) - .all() - ) + ).all() for summary in summaries: if summary.summary_content: segment_summary_map[summary.chunk_id] = summary.summary_content @@ -851,12 +849,12 @@ class RetrievalService: def get_segment_attachment_info( cls, dataset_id: str, tenant_id: str, attachment_id: str, session: Session ) -> SegmentAttachmentResult | None: - upload_file = session.query(UploadFile).where(UploadFile.id == attachment_id).first() + upload_file = session.scalar(select(UploadFile).where(UploadFile.id == attachment_id).limit(1)) if upload_file: - attachment_binding = ( - session.query(SegmentAttachmentBinding) + attachment_binding = session.scalar( + select(SegmentAttachmentBinding) .where(SegmentAttachmentBinding.attachment_id == upload_file.id) - .first() + .limit(1) ) if attachment_binding: attachment_info: AttachmentInfoDict = { @@ -875,14 +873,12 @@ class RetrievalService: cls, attachment_ids: list[str], session: Session ) -> list[SegmentAttachmentInfoResult]: attachment_infos: list[SegmentAttachmentInfoResult] = [] - upload_files = session.query(UploadFile).where(UploadFile.id.in_(attachment_ids)).all() + upload_files = session.scalars(select(UploadFile).where(UploadFile.id.in_(attachment_ids))).all() if upload_files: upload_file_ids = [upload_file.id for upload_file in upload_files] - attachment_bindings = ( - session.query(SegmentAttachmentBinding) - .where(SegmentAttachmentBinding.attachment_id.in_(upload_file_ids)) - .all() - ) + attachment_bindings = session.scalars( + select(SegmentAttachmentBinding).where(SegmentAttachmentBinding.attachment_id.in_(upload_file_ids)) + ).all() attachment_binding_map = {binding.attachment_id: binding for binding in attachment_bindings} if attachment_bindings: diff --git a/api/tests/unit_tests/core/rag/datasource/test_datasource_retrieval.py b/api/tests/unit_tests/core/rag/datasource/test_datasource_retrieval.py index 5dbd62580a..5030b8ea0d 100644 --- a/api/tests/unit_tests/core/rag/datasource/test_datasource_retrieval.py +++ b/api/tests/unit_tests/core/rag/datasource/test_datasource_retrieval.py @@ -119,6 +119,14 @@ class _FakeSummaryQuery: return self._summaries +class _FakeScalarsResult: + def __init__(self, data: list) -> None: + self._data = data + + def all(self) -> list: + return self._data + + class _FakeSession: def __init__(self, execute_payloads: list[list], summaries: list) -> None: self._payloads = list(execute_payloads) @@ -128,8 +136,8 @@ class _FakeSession: data = self._payloads.pop(0) if self._payloads else [] return _FakeExecuteResult(data) - def query(self, model): - return _FakeSummaryQuery(self._summaries) + def scalars(self, stmt): + return _FakeScalarsResult(self._summaries) class _FakeSessionContext: @@ -265,14 +273,14 @@ class TestRetrievalServiceInternals: def test_get_dataset_queries_by_id(self, mock_session_class): expected_dataset = Mock(spec=Dataset) mock_session = Mock() - mock_session.query.return_value.where.return_value.first.return_value = expected_dataset + mock_session.scalar.return_value = expected_dataset mock_session_class.return_value.__enter__.return_value = mock_session with patch.object(retrieval_service_module, "db", SimpleNamespace(engine=Mock())): result = RetrievalService._get_dataset("dataset-123") assert result == expected_dataset - mock_session.query.assert_called_once() + mock_session.scalar.assert_called_once() @patch("core.rag.datasource.retrieval_service.Keyword") @patch("core.rag.datasource.retrieval_service.RetrievalService._get_dataset") @@ -1046,12 +1054,8 @@ class TestRetrievalServiceInternals: size=42, ) binding = SimpleNamespace(segment_id="segment-1", attachment_id="upload-1") - upload_query = Mock() - upload_query.where.return_value.first.return_value = upload_file - binding_query = Mock() - binding_query.where.return_value.first.return_value = binding session = Mock() - session.query.side_effect = [upload_query, binding_query] + session.scalar.side_effect = [upload_file, binding] result = RetrievalService.get_segment_attachment_info("dataset-id", "tenant-id", "upload-1", session) @@ -1076,32 +1080,26 @@ class TestRetrievalServiceInternals: mime_type="image/png", size=42, ) - upload_query = Mock() - upload_query.where.return_value.first.return_value = upload_file - binding_query = Mock() - binding_query.where.return_value.first.return_value = None session = Mock() - session.query.side_effect = [upload_query, binding_query] + session.scalar.side_effect = [upload_file, None] result = RetrievalService.get_segment_attachment_info("dataset-id", "tenant-id", "upload-1", session) assert result is None def test_get_segment_attachment_info_returns_none_when_upload_file_missing(self): - upload_query = Mock() - upload_query.where.return_value.first.return_value = None session = Mock() - session.query.return_value = upload_query + session.scalar.return_value = None result = RetrievalService.get_segment_attachment_info("dataset-id", "tenant-id", "upload-1", session) assert result is None def test_get_segment_attachment_infos_returns_empty_when_upload_files_missing(self): - upload_query = Mock() - upload_query.where.return_value.all.return_value = [] + scalars_result = Mock() + scalars_result.all.return_value = [] session = Mock() - session.query.return_value = upload_query + session.scalars.return_value = scalars_result result = RetrievalService.get_segment_attachment_infos(["upload-1"], session) @@ -1115,12 +1113,12 @@ class TestRetrievalServiceInternals: mime_type="image/png", size=42, ) - upload_query = Mock() - upload_query.where.return_value.all.return_value = [upload_file] - binding_query = Mock() - binding_query.where.return_value.all.return_value = [] + upload_scalars = Mock() + upload_scalars.all.return_value = [upload_file] + binding_scalars = Mock() + binding_scalars.all.return_value = [] session = Mock() - session.query.side_effect = [upload_query, binding_query] + session.scalars.side_effect = [upload_scalars, binding_scalars] result = RetrievalService.get_segment_attachment_infos(["upload-1"], session) @@ -1144,12 +1142,12 @@ class TestRetrievalServiceInternals: ) binding = SimpleNamespace(attachment_id="upload-1", segment_id="segment-1") - upload_query = Mock() - upload_query.where.return_value.all.return_value = [upload_file_1, upload_file_2] - binding_query = Mock() - binding_query.where.return_value.all.return_value = [binding] + upload_scalars = Mock() + upload_scalars.all.return_value = [upload_file_1, upload_file_2] + binding_scalars = Mock() + binding_scalars.all.return_value = [binding] session = Mock() - session.query.side_effect = [upload_query, binding_query] + session.scalars.side_effect = [upload_scalars, binding_scalars] result = RetrievalService.get_segment_attachment_infos(["upload-1", "upload-2"], session) From 459c36f21b5246f38c013c6cf57b92d31ecb981e Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:03:39 +0800 Subject: [PATCH 02/11] fix: improve app delete alert dialog UX (#34644) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- web/app/components/apps/app-card.tsx | 97 ++++++++++++------- .../components/base/ui/alert-dialog/index.tsx | 6 +- .../base/ui/context-menu/index.stories.tsx | 2 +- web/app/components/base/ui/dialog/index.tsx | 8 +- .../base/ui/dropdown-menu/index.tsx | 2 +- .../base/ui/number-field/index.stories.tsx | 6 +- .../components/base/ui/number-field/index.tsx | 6 +- web/app/components/base/ui/popover/index.tsx | 2 +- .../base/ui/scroll-area/index.stories.tsx | 96 +++++++++--------- .../components/base/ui/scroll-area/index.tsx | 4 +- web/app/components/base/ui/select/index.tsx | 10 +- .../base/ui/slider/index.stories.tsx | 2 +- web/app/components/base/ui/slider/index.tsx | 4 +- web/app/components/base/ui/tooltip/index.tsx | 4 +- web/eslint-suppressions.json | 67 +------------ web/i18n/ar-TN/app.json | 4 +- web/i18n/de-DE/app.json | 4 +- web/i18n/en-US/app.json | 4 +- web/i18n/es-ES/app.json | 4 +- web/i18n/fa-IR/app.json | 4 +- web/i18n/fr-FR/app.json | 4 +- web/i18n/hi-IN/app.json | 4 +- web/i18n/id-ID/app.json | 4 +- web/i18n/it-IT/app.json | 4 +- web/i18n/ja-JP/app.json | 4 +- web/i18n/ko-KR/app.json | 4 +- web/i18n/nl-NL/app.json | 4 +- web/i18n/pl-PL/app.json | 4 +- web/i18n/pt-BR/app.json | 4 +- web/i18n/ro-RO/app.json | 4 +- web/i18n/ru-RU/app.json | 4 +- web/i18n/sl-SI/app.json | 4 +- web/i18n/th-TH/app.json | 4 +- web/i18n/tr-TR/app.json | 4 +- web/i18n/uk-UA/app.json | 4 +- web/i18n/vi-VN/app.json | 4 +- web/i18n/zh-Hans/app.json | 4 +- web/i18n/zh-Hant/app.json | 4 +- 38 files changed, 183 insertions(+), 225 deletions(-) diff --git a/web/app/components/apps/app-card.tsx b/web/app/components/apps/app-card.tsx index 7227e412e7..1cc504d746 100644 --- a/web/app/components/apps/app-card.tsx +++ b/web/app/components/apps/app-card.tsx @@ -8,11 +8,12 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types' import type { App } from '@/types/app' import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react' import * as React from 'react' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' +import { useCallback, useEffect, useId, useMemo, useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' import { AppTypeIcon } from '@/app/components/app/type-selector' import AppIcon from '@/app/components/base/app-icon' import Divider from '@/app/components/base/divider' +import Input from '@/app/components/base/input' import CustomPopover from '@/app/components/base/popover' import TagSelector from '@/app/components/base/tag-management/selector' import Tooltip from '@/app/components/base/tooltip' @@ -69,6 +70,7 @@ type AppCardProps = { const AppCard = ({ app, onRefresh }: AppCardProps) => { const { t } = useTranslation() + const deleteAppNameInputId = useId() const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { isCurrentWorkspaceEditor } = useAppContext() const { onPlanInfoChanged } = useProviderContext() @@ -89,14 +91,12 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { await mutateDeleteApp(app.id) toast.success(t('appDeleted', { ns: 'app' })) onPlanInfoChanged() + setShowConfirmDelete(false) + setConfirmDeleteInput('') } catch (e: any) { toast.error(`${t('appDeleteFailed', { ns: 'app' })}${'message' in e ? `: ${e.message}` : ''}`) } - finally { - setShowConfirmDelete(false) - setConfirmDeleteInput('') - } }, [app.id, mutateDeleteApp, onPlanInfoChanged, t]) const onDeleteDialogOpenChange = useCallback((open: boolean) => { @@ -108,6 +108,16 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { setConfirmDeleteInput('') }, [isDeleting]) + const isDeleteConfirmDisabled = isDeleting || confirmDeleteInput !== app.name + + const onDeleteDialogSubmit: React.FormEventHandler = useCallback((e) => { + e.preventDefault() + if (isDeleteConfirmDisabled) + return + + void onConfirmDelete() + }, [isDeleteConfirmDisabled, onConfirmDelete]) + const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ name, icon_type, @@ -503,38 +513,51 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { )} -
- - {t('deleteAppConfirmTitle', { ns: 'app' })} - - - {t('deleteAppConfirmContent', { ns: 'app' })} - -
- - setConfirmDeleteInput(e.target.value)} - /> +
+
+ + {t('deleteAppConfirmTitle', { ns: 'app' })} + + + {t('deleteAppConfirmContent', { ns: 'app' })} + +
+ + setConfirmDeleteInput(e.target.value)} + className="border-components-input-border-hover bg-components-input-bg-normal focus:border-components-input-border-active focus:bg-components-input-bg-active" + /> +
-
- - - {t('operation.cancel', { ns: 'common' })} - - - {t('operation.confirm', { ns: 'common' })} - - + + + {t('operation.cancel', { ns: 'common' })} + + + {t('operation.confirm', { ns: 'common' })} + + + {secretEnvList.length > 0 && ( diff --git a/web/app/components/base/ui/alert-dialog/index.tsx b/web/app/components/base/ui/alert-dialog/index.tsx index db166196e3..df4cef8964 100644 --- a/web/app/components/base/ui/alert-dialog/index.tsx +++ b/web/app/components/base/ui/alert-dialog/index.tsx @@ -32,7 +32,7 @@ export function AlertDialogContent({ diff --git a/web/app/components/base/ui/context-menu/index.stories.tsx b/web/app/components/base/ui/context-menu/index.stories.tsx index 7c57a81c65..084d82bd09 100644 --- a/web/app/components/base/ui/context-menu/index.stories.tsx +++ b/web/app/components/base/ui/context-menu/index.stories.tsx @@ -22,7 +22,7 @@ import { const TriggerArea = ({ label = 'Right-click inside this area' }: { label?: string }) => ( } + render={ ))}
@@ -565,16 +565,16 @@ export const ThreePaneWorkbench: Story = { {Array.from({ length: 7 }, (_, index) => (
-
+
Section {' '} {index + 1}
- + Active
-

+

This pane is intentionally long so the default vertical scrollbar sits over a larger editorial surface.

@@ -585,9 +585,9 @@ export const ThreePaneWorkbench: Story = {
{queueRows.map(item => (
-
{item.id}
-
{item.title}
-
{item.note}
+
{item.id}
+
{item.title}
+
{item.note}
))}
@@ -658,7 +658,7 @@ export const PrimitiveComposition: Story = { {Array.from({ length: 8 }, (_, index) => ( -
+
Primitive row {' '} {index + 1} diff --git a/web/app/components/base/ui/scroll-area/index.tsx b/web/app/components/base/ui/scroll-area/index.tsx index 053d9e684a..34c5e69188 100644 --- a/web/app/components/base/ui/scroll-area/index.tsx +++ b/web/app/components/base/ui/scroll-area/index.tsx @@ -26,7 +26,7 @@ type ScrollAreaProps = Omit & { const scrollAreaScrollbarClassName = cn( styles.scrollbar, - 'flex touch-none select-none overflow-clip p-1 opacity-100 transition-opacity motion-reduce:transition-none', + 'flex touch-none overflow-clip p-1 opacity-100 transition-opacity select-none motion-reduce:transition-none', 'pointer-events-none data-hovering:pointer-events-auto', 'data-scrolling:pointer-events-auto', 'data-[orientation=vertical]:absolute data-[orientation=vertical]:inset-y-0 data-[orientation=vertical]:w-3 data-[orientation=vertical]:justify-center', @@ -41,7 +41,7 @@ const scrollAreaThumbClassName = cn( const scrollAreaViewportClassName = cn( 'size-full min-h-0 min-w-0 outline-hidden', - 'focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-components-input-border-hover', + 'focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:ring-inset', ) const scrollAreaCornerClassName = 'bg-transparent' diff --git a/web/app/components/base/ui/select/index.tsx b/web/app/components/base/ui/select/index.tsx index 7cab541f06..964bd79e08 100644 --- a/web/app/components/base/ui/select/index.tsx +++ b/web/app/components/base/ui/select/index.tsx @@ -84,7 +84,7 @@ export function SelectTrigger({ role="button" aria-label="Clear selection" tabIndex={-1} - className="shrink-0 cursor-pointer text-text-quaternary hover:text-text-secondary group-data-disabled:hidden group-data-readonly:hidden" + className="shrink-0 cursor-pointer text-text-quaternary group-data-disabled:hidden group-data-readonly:hidden hover:text-text-secondary" onClick={(e) => { e.stopPropagation() onClear?.() @@ -97,7 +97,7 @@ export function SelectTrigger({ } else { trailingIcon = ( - + ) @@ -175,7 +175,7 @@ export function SelectContent({ -
+
{value}
diff --git a/web/app/components/base/ui/slider/index.tsx b/web/app/components/base/ui/slider/index.tsx index b0a8c2f376..b514767c2d 100644 --- a/web/app/components/base/ui/slider/index.tsx +++ b/web/app/components/base/ui/slider/index.tsx @@ -28,7 +28,7 @@ type SliderProps = ControlledSliderProps | UncontrolledSliderProps const sliderRootClassName = 'group/slider relative inline-flex w-full data-disabled:opacity-30' const sliderControlClassName = cn( - 'relative flex h-5 w-full touch-none select-none items-center', + 'relative flex h-5 w-full touch-none items-center select-none', 'data-disabled:cursor-not-allowed', ) const sliderTrackClassName = cn( @@ -45,7 +45,7 @@ const sliderThumbClassName = cn( 'bg-(--slider-knob,var(--color-components-slider-knob)) shadow-sm', 'transition-[background-color,border-color,box-shadow,opacity] motion-reduce:transition-none', 'hover:bg-(--slider-knob-hover,var(--color-components-slider-knob-hover))', - 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-components-slider-knob-border-hover focus-visible:ring-offset-0', + 'focus-visible:ring-2 focus-visible:ring-components-slider-knob-border-hover focus-visible:ring-offset-0 focus-visible:outline-hidden', 'active:shadow-md', 'group-data-disabled/slider:bg-(--slider-knob-disabled,var(--color-components-slider-knob-disabled))', 'group-data-disabled/slider:border-(--slider-knob-border,var(--color-components-slider-knob-border))', diff --git a/web/app/components/base/ui/tooltip/index.tsx b/web/app/components/base/ui/tooltip/index.tsx index 693a61ca1f..030c30bf78 100644 --- a/web/app/components/base/ui/tooltip/index.tsx +++ b/web/app/components/base/ui/tooltip/index.tsx @@ -41,8 +41,8 @@ export function TooltipContent({ > {{appName}} في المربع أدناه:", + "deleteAppConfirmInputPlaceholder": "أدخل اسم التطبيق…", "deleteAppConfirmTitle": "حذف هذا التطبيق؟", "dslUploader.browse": "تصفح", "dslUploader.button": "اسحب وأفلت الملف، أو", diff --git a/web/i18n/de-DE/app.json b/web/i18n/de-DE/app.json index 8af6239c47..ab391b94ec 100644 --- a/web/i18n/de-DE/app.json +++ b/web/i18n/de-DE/app.json @@ -36,8 +36,8 @@ "createApp": "Neue App erstellen", "createFromConfigFile": "App aus Konfigurationsdatei erstellen", "deleteAppConfirmContent": "Das Löschen der App ist unwiderruflich. Nutzer werden keinen Zugang mehr zu Ihrer App haben, und alle Prompt-Konfigurationen und Logs werden dauerhaft gelöscht.", - "deleteAppConfirmInputLabel": "Geben Sie zur Bestätigung \"{{appName}}\" in das Feld unten ein:", - "deleteAppConfirmInputPlaceholder": "App-Namen eingeben", + "deleteAppConfirmInputLabel": "Geben Sie zur Bestätigung {{appName}} in das Feld unten ein:", + "deleteAppConfirmInputPlaceholder": "App-Namen eingeben…", "deleteAppConfirmTitle": "Diese App löschen?", "dslUploader.browse": "Durchsuchen", "dslUploader.button": "Datei per Drag & Drop ablegen oder", diff --git a/web/i18n/en-US/app.json b/web/i18n/en-US/app.json index f399c5961d..0c3b35ba14 100644 --- a/web/i18n/en-US/app.json +++ b/web/i18n/en-US/app.json @@ -36,8 +36,8 @@ "createApp": "CREATE APP", "createFromConfigFile": "Create from DSL file", "deleteAppConfirmContent": "Deleting the app is irreversible. Users will no longer be able to access your app, and all prompt configurations and logs will be permanently deleted.", - "deleteAppConfirmInputLabel": "To confirm, type \"{{appName}}\" in the box below:", - "deleteAppConfirmInputPlaceholder": "Enter app name", + "deleteAppConfirmInputLabel": "To confirm, type {{appName}} in the box below:", + "deleteAppConfirmInputPlaceholder": "Enter app name…", "deleteAppConfirmTitle": "Delete this app?", "dslUploader.browse": "Browse", "dslUploader.button": "Drag and drop file, or", diff --git a/web/i18n/es-ES/app.json b/web/i18n/es-ES/app.json index 5dece4801f..628358644c 100644 --- a/web/i18n/es-ES/app.json +++ b/web/i18n/es-ES/app.json @@ -36,8 +36,8 @@ "createApp": "CREAR APP", "createFromConfigFile": "Crear desde archivo DSL", "deleteAppConfirmContent": "Eliminar la app es irreversible. Los usuarios ya no podrán acceder a tu app y todas las configuraciones y registros de prompts se eliminarán permanentemente.", - "deleteAppConfirmInputLabel": "Para confirmar, escriba \"{{appName}}\" en el cuadro a continuación:", - "deleteAppConfirmInputPlaceholder": "Ingrese el nombre de la app", + "deleteAppConfirmInputLabel": "Para confirmar, escriba {{appName}} en el cuadro a continuación:", + "deleteAppConfirmInputPlaceholder": "Ingrese el nombre de la app…", "deleteAppConfirmTitle": "¿Eliminar esta app?", "dslUploader.browse": "Examinar", "dslUploader.button": "Arrastrar y soltar archivo, o", diff --git a/web/i18n/fa-IR/app.json b/web/i18n/fa-IR/app.json index a07a08bda8..42e52f105b 100644 --- a/web/i18n/fa-IR/app.json +++ b/web/i18n/fa-IR/app.json @@ -36,8 +36,8 @@ "createApp": "ایجاد برنامه", "createFromConfigFile": "ایجاد از فایل DSL", "deleteAppConfirmContent": "حذف برنامه غیرقابل برگشت است. کاربران دیگر قادر به دسترسی به برنامه شما نخواهند بود و تمام تنظیمات و گزارشات درخواست‌ها به صورت دائم حذف خواهند شد.", - "deleteAppConfirmInputLabel": "برای تأیید، \"{{appName}}\" را در کادر زیر تایپ کنید:", - "deleteAppConfirmInputPlaceholder": "نام برنامه را وارد کنید", + "deleteAppConfirmInputLabel": "برای تأیید، {{appName}} را در کادر زیر تایپ کنید:", + "deleteAppConfirmInputPlaceholder": "نام برنامه را وارد کنید…", "deleteAppConfirmTitle": "آیا این برنامه حذف شود؟", "dslUploader.browse": "مرور", "dslUploader.button": "فایل را بکشید و رها کنید، یا", diff --git a/web/i18n/fr-FR/app.json b/web/i18n/fr-FR/app.json index 056aa5be0a..0bf579645d 100644 --- a/web/i18n/fr-FR/app.json +++ b/web/i18n/fr-FR/app.json @@ -36,8 +36,8 @@ "createApp": "CRÉER UNE APPLICATION", "createFromConfigFile": "Créer à partir du fichier DSL", "deleteAppConfirmContent": "La suppression de l'application est irréversible. Les utilisateurs ne pourront plus accéder à votre application et toutes les configurations de prompt et les journaux seront définitivement supprimés.", - "deleteAppConfirmInputLabel": "Pour confirmer, tapez \"{{appName}}\" dans la case ci-dessous :", - "deleteAppConfirmInputPlaceholder": "Entrez le nom de l'application", + "deleteAppConfirmInputLabel": "Pour confirmer, tapez {{appName}} dans la case ci-dessous :", + "deleteAppConfirmInputPlaceholder": "Entrez le nom de l'application…", "deleteAppConfirmTitle": "Supprimer cette application ?", "dslUploader.browse": "Parcourir", "dslUploader.button": "Glisser-déposer un fichier, ou", diff --git a/web/i18n/hi-IN/app.json b/web/i18n/hi-IN/app.json index a6b3bbe446..be6cac2126 100644 --- a/web/i18n/hi-IN/app.json +++ b/web/i18n/hi-IN/app.json @@ -36,8 +36,8 @@ "createApp": "ऐप बनाएँ", "createFromConfigFile": "डीएसएल फ़ाइल से बनाएँ", "deleteAppConfirmContent": "ऐप को हटाना अपरिवर्तनीय है। उपयोगकर्ता अब आपके ऐप तक पहुँचने में सक्षम नहीं होंगे, और सभी प्रॉम्प्ट कॉन्फ़िगरेशन और लॉग स्थायी रूप से हटा दिए जाएंगे।", - "deleteAppConfirmInputLabel": "पुष्टि करने के लिए, नीचे दिए गए बॉक्स में \"{{appName}}\" टाइप करें:", - "deleteAppConfirmInputPlaceholder": "ऐप का नाम दर्ज करें", + "deleteAppConfirmInputLabel": "पुष्टि करने के लिए, नीचे दिए गए बॉक्स में {{appName}} टाइप करें:", + "deleteAppConfirmInputPlaceholder": "ऐप का नाम दर्ज करें…", "deleteAppConfirmTitle": "इस ऐप को हटाएँ?", "dslUploader.browse": "ब्राउज़ करें", "dslUploader.button": "फ़ाइल खींचकर छोड़ें, या", diff --git a/web/i18n/id-ID/app.json b/web/i18n/id-ID/app.json index d6249cb2d2..f9a9f66eaf 100644 --- a/web/i18n/id-ID/app.json +++ b/web/i18n/id-ID/app.json @@ -36,8 +36,8 @@ "createApp": "BUAT APLIKASI", "createFromConfigFile": "Buat dari file DSL", "deleteAppConfirmContent": "Menghapus aplikasi tidak dapat diubah. Pengguna tidak akan dapat lagi mengakses aplikasi Anda, dan semua konfigurasi prompt serta log akan dihapus secara permanen.", - "deleteAppConfirmInputLabel": "Untuk konfirmasi, ketik \"{{appName}}\" di kotak di bawah ini:", - "deleteAppConfirmInputPlaceholder": "Masukkan nama aplikasi", + "deleteAppConfirmInputLabel": "Untuk konfirmasi, ketik {{appName}} di kotak di bawah ini:", + "deleteAppConfirmInputPlaceholder": "Masukkan nama aplikasi…", "deleteAppConfirmTitle": "Hapus aplikasi ini?", "dslUploader.browse": "Ramban", "dslUploader.button": "Seret dan lepas file, atau", diff --git a/web/i18n/it-IT/app.json b/web/i18n/it-IT/app.json index 0364768909..feb70eddf1 100644 --- a/web/i18n/it-IT/app.json +++ b/web/i18n/it-IT/app.json @@ -36,8 +36,8 @@ "createApp": "CREA APP", "createFromConfigFile": "Crea da file DSL", "deleteAppConfirmContent": "Eliminare l'app è irreversibile. Gli utenti non potranno più accedere alla tua app e tutte le configurazioni e i log dei prompt verranno eliminati permanentemente.", - "deleteAppConfirmInputLabel": "Per confermare, digita \"{{appName}}\" nel campo sottostante:", - "deleteAppConfirmInputPlaceholder": "Inserisci il nome dell'app", + "deleteAppConfirmInputLabel": "Per confermare, digita {{appName}} nel campo sottostante:", + "deleteAppConfirmInputPlaceholder": "Inserisci il nome dell'app…", "deleteAppConfirmTitle": "Eliminare questa app?", "dslUploader.browse": "Sfoglia", "dslUploader.button": "Trascina e rilascia il file, o", diff --git a/web/i18n/ja-JP/app.json b/web/i18n/ja-JP/app.json index ca34df1b3f..984b1d16e9 100644 --- a/web/i18n/ja-JP/app.json +++ b/web/i18n/ja-JP/app.json @@ -36,8 +36,8 @@ "createApp": "アプリを作成する", "createFromConfigFile": "DSL ファイルから作成する", "deleteAppConfirmContent": "アプリを削除すると、元に戻すことはできません。他のユーザーはもはやこのアプリにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。", - "deleteAppConfirmInputLabel": "確認するには、下のボックスに「{{appName}}」と入力してください:", - "deleteAppConfirmInputPlaceholder": "アプリ名を入力", + "deleteAppConfirmInputLabel": "確認するには、下のボックスに{{appName}}と入力してください:", + "deleteAppConfirmInputPlaceholder": "アプリ名を入力…", "deleteAppConfirmTitle": "このアプリを削除しますか?", "dslUploader.browse": "参照", "dslUploader.button": "ファイルをドラッグ&ドロップするか、", diff --git a/web/i18n/ko-KR/app.json b/web/i18n/ko-KR/app.json index a13699442b..2f13441a84 100644 --- a/web/i18n/ko-KR/app.json +++ b/web/i18n/ko-KR/app.json @@ -36,8 +36,8 @@ "createApp": "앱 만들기", "createFromConfigFile": "DSL 파일에서 생성하기", "deleteAppConfirmContent": "앱을 삭제하면 복구할 수 없습니다. 사용자는 더 이상 앱에 액세스할 수 없으며 모든 프롬프트 설정 및 로그가 영구적으로 삭제됩니다.", - "deleteAppConfirmInputLabel": "확인하려면 아래 상자에 \"{{appName}}\"을 입력하세요:", - "deleteAppConfirmInputPlaceholder": "앱 이름 입력", + "deleteAppConfirmInputLabel": "확인하려면 아래 상자에 {{appName}}을 입력하세요:", + "deleteAppConfirmInputPlaceholder": "앱 이름 입력…", "deleteAppConfirmTitle": "이 앱을 삭제하시겠습니까?", "dslUploader.browse": "찾아보기", "dslUploader.button": "파일을 드래그 앤 드롭하거나", diff --git a/web/i18n/nl-NL/app.json b/web/i18n/nl-NL/app.json index f399c5961d..0c3b35ba14 100644 --- a/web/i18n/nl-NL/app.json +++ b/web/i18n/nl-NL/app.json @@ -36,8 +36,8 @@ "createApp": "CREATE APP", "createFromConfigFile": "Create from DSL file", "deleteAppConfirmContent": "Deleting the app is irreversible. Users will no longer be able to access your app, and all prompt configurations and logs will be permanently deleted.", - "deleteAppConfirmInputLabel": "To confirm, type \"{{appName}}\" in the box below:", - "deleteAppConfirmInputPlaceholder": "Enter app name", + "deleteAppConfirmInputLabel": "To confirm, type {{appName}} in the box below:", + "deleteAppConfirmInputPlaceholder": "Enter app name…", "deleteAppConfirmTitle": "Delete this app?", "dslUploader.browse": "Browse", "dslUploader.button": "Drag and drop file, or", diff --git a/web/i18n/pl-PL/app.json b/web/i18n/pl-PL/app.json index 9539db9a58..9b0cdf76ef 100644 --- a/web/i18n/pl-PL/app.json +++ b/web/i18n/pl-PL/app.json @@ -36,8 +36,8 @@ "createApp": "UTWÓRZ APLIKACJĘ", "createFromConfigFile": "Utwórz z pliku DSL", "deleteAppConfirmContent": "Usunięcie aplikacji jest nieodwracalne. Użytkownicy nie będą mieli już dostępu do twojej aplikacji, a wszystkie konfiguracje monitów i dzienniki zostaną trwale usunięte.", - "deleteAppConfirmInputLabel": "Aby potwierdzić, wpisz \"{{appName}}\" w polu poniżej:", - "deleteAppConfirmInputPlaceholder": "Wpisz nazwę aplikacji", + "deleteAppConfirmInputLabel": "Aby potwierdzić, wpisz {{appName}} w polu poniżej:", + "deleteAppConfirmInputPlaceholder": "Wpisz nazwę aplikacji…", "deleteAppConfirmTitle": "Usunąć tę aplikację?", "dslUploader.browse": "Przeglądaj", "dslUploader.button": "Przeciągnij i upuść plik, lub", diff --git a/web/i18n/pt-BR/app.json b/web/i18n/pt-BR/app.json index 9d6fd0b52c..3b18d57031 100644 --- a/web/i18n/pt-BR/app.json +++ b/web/i18n/pt-BR/app.json @@ -36,8 +36,8 @@ "createApp": "CRIAR APLICATIVO", "createFromConfigFile": "Criar a partir do arquivo DSL", "deleteAppConfirmContent": "A exclusão do aplicativo é irreversível. Os usuários não poderão mais acessar seu aplicativo e todas as configurações de prompt e logs serão permanentemente excluídas.", - "deleteAppConfirmInputLabel": "Para confirmar, digite \"{{appName}}\" na caixa abaixo:", - "deleteAppConfirmInputPlaceholder": "Digite o nome do aplicativo", + "deleteAppConfirmInputLabel": "Para confirmar, digite {{appName}} na caixa abaixo:", + "deleteAppConfirmInputPlaceholder": "Digite o nome do aplicativo…", "deleteAppConfirmTitle": "Excluir este aplicativo?", "dslUploader.browse": "Navegar", "dslUploader.button": "Arraste e solte o arquivo, ou", diff --git a/web/i18n/ro-RO/app.json b/web/i18n/ro-RO/app.json index de0ecf5f63..32c08e9f54 100644 --- a/web/i18n/ro-RO/app.json +++ b/web/i18n/ro-RO/app.json @@ -36,8 +36,8 @@ "createApp": "CREEAZĂ APLICAȚIE", "createFromConfigFile": "Creează din fișier DSL", "deleteAppConfirmContent": "Ștergerea aplicației este ireversibilă. Utilizatorii nu vor mai putea accesa aplicația ta, iar toate configurațiile promptului și jurnalele vor fi șterse permanent.", - "deleteAppConfirmInputLabel": "Pentru confirmare, tastați \"{{appName}}\" în caseta de mai jos:", - "deleteAppConfirmInputPlaceholder": "Introduceți numele aplicației", + "deleteAppConfirmInputLabel": "Pentru confirmare, tastați {{appName}} în caseta de mai jos:", + "deleteAppConfirmInputPlaceholder": "Introduceți numele aplicației…", "deleteAppConfirmTitle": "Ștergi această aplicație?", "dslUploader.browse": "Răsfoiți", "dslUploader.button": "Trageți și plasați fișierul, sau", diff --git a/web/i18n/ru-RU/app.json b/web/i18n/ru-RU/app.json index 8f275934c2..faf79574ae 100644 --- a/web/i18n/ru-RU/app.json +++ b/web/i18n/ru-RU/app.json @@ -36,8 +36,8 @@ "createApp": "СОЗДАТЬ ПРИЛОЖЕНИЕ", "createFromConfigFile": "Создать из файла DSL", "deleteAppConfirmContent": "Удаление приложения необратимо. Пользователи больше не смогут получить доступ к вашему приложению, и все настройки подсказок и журналы будут безвозвратно удалены.", - "deleteAppConfirmInputLabel": "Для подтверждения введите \"{{appName}}\" в поле ниже:", - "deleteAppConfirmInputPlaceholder": "Введите название приложения", + "deleteAppConfirmInputLabel": "Для подтверждения введите {{appName}} в поле ниже:", + "deleteAppConfirmInputPlaceholder": "Введите название приложения…", "deleteAppConfirmTitle": "Удалить это приложение?", "dslUploader.browse": "Обзор", "dslUploader.button": "Перетащите файл, или", diff --git a/web/i18n/sl-SI/app.json b/web/i18n/sl-SI/app.json index c4f9c02bda..f3016ce58f 100644 --- a/web/i18n/sl-SI/app.json +++ b/web/i18n/sl-SI/app.json @@ -36,8 +36,8 @@ "createApp": "USTVARI APLIKACIJO", "createFromConfigFile": "Ustvari iz datoteke DSL", "deleteAppConfirmContent": "Brisanje aplikacije je nepopravljivo. Uporabniki ne bodo več imeli dostopa do vaše aplikacije, vse konfiguracije in dnevniki pa bodo trajno izbrisani.", - "deleteAppConfirmInputLabel": "Za potrditev vnesite \"{{appName}}\" v polje spodaj:", - "deleteAppConfirmInputPlaceholder": "Vnesite ime aplikacije", + "deleteAppConfirmInputLabel": "Za potrditev vnesite {{appName}} v polje spodaj:", + "deleteAppConfirmInputPlaceholder": "Vnesite ime aplikacije…", "deleteAppConfirmTitle": "Izbrišem to aplikacijo?", "dslUploader.browse": "Prebrskaj", "dslUploader.button": "Povlecite in spustite datoteko, ali", diff --git a/web/i18n/th-TH/app.json b/web/i18n/th-TH/app.json index aa3c67a178..c5cf1722a3 100644 --- a/web/i18n/th-TH/app.json +++ b/web/i18n/th-TH/app.json @@ -36,8 +36,8 @@ "createApp": "สร้างโปรเจกต์ใหม่", "createFromConfigFile": "สร้างจากไฟล์ DSL", "deleteAppConfirmContent": "การลบโปรเจกนั้นไม่สามารถย้อนกลับได้ ผู้ใช้จะไม่สามารถเข้าถึงโปรเจกต์ของคุณอีกต่อไป และการกําหนดค่าต่างๆและบันทึกทั้งหมดจะถูกลบอย่างถาวร", - "deleteAppConfirmInputLabel": "หากต้องการยืนยัน พิมพ์ \"{{appName}}\" ในช่องด้านล่าง:", - "deleteAppConfirmInputPlaceholder": "ใส่ชื่อแอป", + "deleteAppConfirmInputLabel": "หากต้องการยืนยัน พิมพ์ {{appName}} ในช่องด้านล่าง:", + "deleteAppConfirmInputPlaceholder": "ใส่ชื่อแอป…", "deleteAppConfirmTitle": "ลบโปรเจกต์นี้?", "dslUploader.browse": "เรียกดู", "dslUploader.button": "ลากและวางไฟล์ หรือ", diff --git a/web/i18n/tr-TR/app.json b/web/i18n/tr-TR/app.json index bf17583c47..8006ee6467 100644 --- a/web/i18n/tr-TR/app.json +++ b/web/i18n/tr-TR/app.json @@ -36,8 +36,8 @@ "createApp": "UYGULAMA OLUŞTUR", "createFromConfigFile": "DSL dosyasından oluştur", "deleteAppConfirmContent": "Uygulamanın silinmesi geri alınamaz. Kullanıcılar artık uygulamanıza erişemeyecek ve tüm prompt yapılandırmaları ile loglar kalıcı olarak silinecektir.", - "deleteAppConfirmInputLabel": "Onaylamak için aşağıdaki kutuya \"{{appName}}\" yazın:", - "deleteAppConfirmInputPlaceholder": "Uygulama adını girin", + "deleteAppConfirmInputLabel": "Onaylamak için aşağıdaki kutuya {{appName}} yazın:", + "deleteAppConfirmInputPlaceholder": "Uygulama adını girin…", "deleteAppConfirmTitle": "Bu uygulamayı silmek istiyor musunuz?", "dslUploader.browse": "Gözat", "dslUploader.button": "Dosyayı sürükleyip bırakın veya", diff --git a/web/i18n/uk-UA/app.json b/web/i18n/uk-UA/app.json index 9633000fea..db85bf338c 100644 --- a/web/i18n/uk-UA/app.json +++ b/web/i18n/uk-UA/app.json @@ -36,8 +36,8 @@ "createApp": "Створити додаток", "createFromConfigFile": "Створити з файлу DSL", "deleteAppConfirmContent": "Видалення додатка незворотнє. Користувачі більше не зможуть отримати доступ до вашого додатка, і всі налаштування запитів та журнали будуть остаточно видалені.", - "deleteAppConfirmInputLabel": "Для підтвердження введіть \"{{appName}}\" у поле нижче:", - "deleteAppConfirmInputPlaceholder": "Введіть назву додатка", + "deleteAppConfirmInputLabel": "Для підтвердження введіть {{appName}} у поле нижче:", + "deleteAppConfirmInputPlaceholder": "Введіть назву додатка…", "deleteAppConfirmTitle": "Видалити цей додаток?", "dslUploader.browse": "Огляд", "dslUploader.button": "Перетягніть файл, або", diff --git a/web/i18n/vi-VN/app.json b/web/i18n/vi-VN/app.json index 527b69e79d..26b2448fb5 100644 --- a/web/i18n/vi-VN/app.json +++ b/web/i18n/vi-VN/app.json @@ -36,8 +36,8 @@ "createApp": "TẠO ỨNG DỤNG", "createFromConfigFile": "Tạo từ tệp DSL", "deleteAppConfirmContent": "Việc xóa ứng dụng là không thể hoàn tác. Người dùng sẽ không thể truy cập vào ứng dụng của bạn nữa và tất cả cấu hình cũng như nhật ký nhắc sẽ bị xóa vĩnh viễn.", - "deleteAppConfirmInputLabel": "Để xác nhận, hãy nhập \"{{appName}}\" vào ô bên dưới:", - "deleteAppConfirmInputPlaceholder": "Nhập tên ứng dụng", + "deleteAppConfirmInputLabel": "Để xác nhận, hãy nhập {{appName}} vào ô bên dưới:", + "deleteAppConfirmInputPlaceholder": "Nhập tên ứng dụng…", "deleteAppConfirmTitle": "Xóa ứng dụng này?", "dslUploader.browse": "Duyệt", "dslUploader.button": "Kéo và thả tệp, hoặc", diff --git a/web/i18n/zh-Hans/app.json b/web/i18n/zh-Hans/app.json index 92c5f15c79..2eb0f231b8 100644 --- a/web/i18n/zh-Hans/app.json +++ b/web/i18n/zh-Hans/app.json @@ -36,8 +36,8 @@ "createApp": "创建应用", "createFromConfigFile": "通过 DSL 文件创建", "deleteAppConfirmContent": "删除应用将无法撤销。用户将不能访问你的应用,所有 Prompt 编排配置和日志均将一并被删除。", - "deleteAppConfirmInputLabel": "请在下方输入框中输入\"{{appName}}\"以确认:", - "deleteAppConfirmInputPlaceholder": "输入应用名称", + "deleteAppConfirmInputLabel": "请在下方输入框中输入{{appName}}以确认:", + "deleteAppConfirmInputPlaceholder": "输入应用名称…", "deleteAppConfirmTitle": "确认删除应用?", "dslUploader.browse": "选择文件", "dslUploader.button": "拖拽文件至此,或者", diff --git a/web/i18n/zh-Hant/app.json b/web/i18n/zh-Hant/app.json index 0b7e9691a9..fba07be1bc 100644 --- a/web/i18n/zh-Hant/app.json +++ b/web/i18n/zh-Hant/app.json @@ -36,8 +36,8 @@ "createApp": "建立應用", "createFromConfigFile": "透過 DSL 檔案建立", "deleteAppConfirmContent": "刪除應用將無法復原。使用者將無法存取你的應用,所有 Prompt 設定和日誌都將一併被刪除。", - "deleteAppConfirmInputLabel": "請在下方輸入框中輸入「{{appName}}」以確認:", - "deleteAppConfirmInputPlaceholder": "輸入應用程式名稱", + "deleteAppConfirmInputLabel": "請在下方輸入框中輸入{{appName}}以確認:", + "deleteAppConfirmInputPlaceholder": "輸入應用程式名稱…", "deleteAppConfirmTitle": "確認刪除應用?", "dslUploader.browse": "選擇檔案", "dslUploader.button": "拖拽檔案至此,或者", From 3e995e6a6df3a275b1b45c574d63424f360f403c Mon Sep 17 00:00:00 2001 From: Renzo <170978465+RenzoMXD@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:53:21 -0500 Subject: [PATCH 03/11] refactor: migrate session.query to select API in document task files (#34646) --- api/tasks/clean_notion_document_task.py | 4 ++-- api/tasks/document_indexing_update_task.py | 6 ++++-- api/tasks/retry_document_indexing_task.py | 16 +++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/tasks/clean_notion_document_task.py b/api/tasks/clean_notion_document_task.py index c22ee761d8..e3be24ac74 100644 --- a/api/tasks/clean_notion_document_task.py +++ b/api/tasks/clean_notion_document_task.py @@ -26,7 +26,7 @@ def clean_notion_document_task(document_ids: list[str], dataset_id: str): total_index_node_ids = [] with session_factory.create_session() as session: - dataset = session.query(Dataset).where(Dataset.id == dataset_id).first() + dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1)) if not dataset: raise Exception("Document has no dataset") @@ -41,7 +41,7 @@ def clean_notion_document_task(document_ids: list[str], dataset_id: str): total_index_node_ids.extend([segment.index_node_id for segment in segments]) with session_factory.create_session() as session: - dataset = session.query(Dataset).where(Dataset.id == dataset_id).first() + dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1)) if dataset: index_processor.clean( dataset, total_index_node_ids, with_keywords=True, delete_child_chunks=True, delete_summaries=True diff --git a/api/tasks/document_indexing_update_task.py b/api/tasks/document_indexing_update_task.py index 62bce24de4..15f0e0162b 100644 --- a/api/tasks/document_indexing_update_task.py +++ b/api/tasks/document_indexing_update_task.py @@ -28,7 +28,9 @@ def document_indexing_update_task(dataset_id: str, document_id: str): start_at = time.perf_counter() with session_factory.create_session() as session, session.begin(): - document = session.query(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).first() + document = session.scalar( + select(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).limit(1) + ) if not document: logger.info(click.style(f"Document not found: {document_id}", fg="red")) @@ -37,7 +39,7 @@ def document_indexing_update_task(dataset_id: str, document_id: str): document.indexing_status = IndexingStatus.PARSING document.processing_started_at = naive_utc_now() - dataset = session.query(Dataset).where(Dataset.id == dataset_id).first() + dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1)) if not dataset: return diff --git a/api/tasks/retry_document_indexing_task.py b/api/tasks/retry_document_indexing_task.py index 4fcb0cf804..7cc28d5226 100644 --- a/api/tasks/retry_document_indexing_task.py +++ b/api/tasks/retry_document_indexing_task.py @@ -32,15 +32,15 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str], user_ start_at = time.perf_counter() with session_factory.create_session() as session: try: - dataset = session.query(Dataset).where(Dataset.id == dataset_id).first() + dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1)) if not dataset: logger.info(click.style(f"Dataset not found: {dataset_id}", fg="red")) return - user = session.query(Account).where(Account.id == user_id).first() + user = session.scalar(select(Account).where(Account.id == user_id).limit(1)) if not user: logger.info(click.style(f"User not found: {user_id}", fg="red")) return - tenant = session.query(Tenant).where(Tenant.id == dataset.tenant_id).first() + tenant = session.scalar(select(Tenant).where(Tenant.id == dataset.tenant_id).limit(1)) if not tenant: raise ValueError("Tenant not found") user.current_tenant = tenant @@ -58,10 +58,8 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str], user_ "your subscription." ) except Exception as e: - document = ( - session.query(Document) - .where(Document.id == document_id, Document.dataset_id == dataset_id) - .first() + document = session.scalar( + select(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).limit(1) ) if document: document.indexing_status = IndexingStatus.ERROR @@ -73,8 +71,8 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str], user_ return logger.info(click.style(f"Start retry document: {document_id}", fg="green")) - document = ( - session.query(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).first() + document = session.scalar( + select(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).limit(1) ) if not document: logger.info(click.style(f"Document not found: {document_id}", fg="yellow")) From 84d8940dbf12b9f2d7b08da9b40fc440bf9a43f5 Mon Sep 17 00:00:00 2001 From: YBoy Date: Tue, 7 Apr 2026 07:53:50 +0200 Subject: [PATCH 04/11] =?UTF-8?q?refactor(api):=20type=20app=20parameter?= =?UTF-8?q?=20feature=20toggles=20with=20FeatureToggleD=E2=80=A6=20(#34651?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/parameters_mapping/__init__.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/api/core/app/app_config/common/parameters_mapping/__init__.py b/api/core/app/app_config/common/parameters_mapping/__init__.py index 460fdfb3ba..68686ceda6 100644 --- a/api/core/app/app_config/common/parameters_mapping/__init__.py +++ b/api/core/app/app_config/common/parameters_mapping/__init__.py @@ -5,6 +5,10 @@ from configs import dify_config from constants import DEFAULT_FILE_NUMBER_LIMITS +class FeatureToggleDict(TypedDict): + enabled: bool + + class SystemParametersDict(TypedDict): image_file_size_limit: int video_file_size_limit: int @@ -16,12 +20,12 @@ class SystemParametersDict(TypedDict): class AppParametersDict(TypedDict): opening_statement: str | None suggested_questions: list[str] - suggested_questions_after_answer: dict[str, Any] - speech_to_text: dict[str, Any] - text_to_speech: dict[str, Any] - retriever_resource: dict[str, Any] - annotation_reply: dict[str, Any] - more_like_this: dict[str, Any] + suggested_questions_after_answer: FeatureToggleDict + speech_to_text: FeatureToggleDict + text_to_speech: FeatureToggleDict + retriever_resource: FeatureToggleDict + annotation_reply: FeatureToggleDict + more_like_this: FeatureToggleDict user_input_form: list[dict[str, Any]] sensitive_word_avoidance: dict[str, Any] file_upload: dict[str, Any] From 173e818a626227f77c04cf78cbef4b40ba1dc13e Mon Sep 17 00:00:00 2001 From: Renzo <170978465+RenzoMXD@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:55:31 -0500 Subject: [PATCH 05/11] refactor: migrate session.query to select API in summary and remove document tasks (#34650) --- api/tasks/generate_summary_index_task.py | 20 ++++++++++---------- api/tasks/remove_document_from_index_task.py | 20 +++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/api/tasks/generate_summary_index_task.py b/api/tasks/generate_summary_index_task.py index e3d82d2851..9eda5716b8 100644 --- a/api/tasks/generate_summary_index_task.py +++ b/api/tasks/generate_summary_index_task.py @@ -5,6 +5,7 @@ import time import click from celery import shared_task +from sqlalchemy import select, update from core.db.session_factory import session_factory from core.rag.index_processor.constant.index_type import IndexTechniqueType @@ -39,12 +40,12 @@ def generate_summary_index_task(dataset_id: str, document_id: str, segment_ids: try: with session_factory.create_session() as session: - dataset = session.query(Dataset).where(Dataset.id == dataset_id).first() + dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1)) if not dataset: logger.error(click.style(f"Dataset not found: {dataset_id}", fg="red")) return - document = session.query(DatasetDocument).where(DatasetDocument.id == document_id).first() + document = session.scalar(select(DatasetDocument).where(DatasetDocument.id == document_id).limit(1)) if not document: logger.error(click.style(f"Document not found: {document_id}", fg="red")) return @@ -108,13 +109,12 @@ def generate_summary_index_task(dataset_id: str, document_id: str, segment_ids: if segment_ids: error_message = f"Summary generation failed: {str(e)}" with session_factory.create_session() as session: - session.query(DocumentSegment).filter( - DocumentSegment.id.in_(segment_ids), - DocumentSegment.dataset_id == dataset_id, - ).update( - { - DocumentSegment.error: error_message, - }, - synchronize_session=False, + session.execute( + update(DocumentSegment) + .where( + DocumentSegment.id.in_(segment_ids), + DocumentSegment.dataset_id == dataset_id, + ) + .values(error=error_message) ) session.commit() diff --git a/api/tasks/remove_document_from_index_task.py b/api/tasks/remove_document_from_index_task.py index 55259ab527..74e8a012cf 100644 --- a/api/tasks/remove_document_from_index_task.py +++ b/api/tasks/remove_document_from_index_task.py @@ -3,7 +3,7 @@ import time import click from celery import shared_task -from sqlalchemy import select +from sqlalchemy import select, update from core.db.session_factory import session_factory from core.rag.index_processor.index_processor_factory import IndexProcessorFactory @@ -26,7 +26,7 @@ def remove_document_from_index_task(document_id: str): start_at = time.perf_counter() with session_factory.create_session() as session: - document = session.query(Document).where(Document.id == document_id).first() + document = session.scalar(select(Document).where(Document.id == document_id).limit(1)) if not document: logger.info(click.style(f"Document not found: {document_id}", fg="red")) return @@ -68,13 +68,15 @@ def remove_document_from_index_task(document_id: str): except Exception: logger.exception("clean dataset %s from index failed", dataset.id) # update segment to disable - session.query(DocumentSegment).where(DocumentSegment.document_id == document.id).update( - { - DocumentSegment.enabled: False, - DocumentSegment.disabled_at: naive_utc_now(), - DocumentSegment.disabled_by: document.disabled_by, - DocumentSegment.updated_at: naive_utc_now(), - } + session.execute( + update(DocumentSegment) + .where(DocumentSegment.document_id == document.id) + .values( + enabled=False, + disabled_at=naive_utc_now(), + disabled_by=document.disabled_by, + updated_at=naive_utc_now(), + ) ) session.commit() From bceb0eee9bf9cf8bd947ce9a26b519cfcc0b815a Mon Sep 17 00:00:00 2001 From: Pulakesh <84331848+iamPulakesh@users.noreply.github.com> Date: Tue, 7 Apr 2026 05:56:02 +0000 Subject: [PATCH 06/11] refactor(api): migrate dict returns to TypedDicts in billing service (#34649) --- api/controllers/console/admin.py | 5 +-- api/services/billing_service.py | 56 ++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/api/controllers/console/admin.py b/api/controllers/console/admin.py index 9b8408980d..dce394be97 100644 --- a/api/controllers/console/admin.py +++ b/api/controllers/console/admin.py @@ -2,6 +2,7 @@ import csv import io from collections.abc import Callable from functools import wraps +from typing import cast from flask import request from flask_restx import Resource @@ -17,7 +18,7 @@ from core.db.session_factory import session_factory from extensions.ext_database import db from libs.token import extract_access_token from models.model import App, ExporleBanner, InstalledApp, RecommendedApp, TrialApp -from services.billing_service import BillingService +from services.billing_service import BillingService, LangContentDict DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}" @@ -328,7 +329,7 @@ class UpsertNotificationApi(Resource): def post(self): payload = UpsertNotificationPayload.model_validate(console_ns.payload) result = BillingService.upsert_notification( - contents=[c.model_dump() for c in payload.contents], + contents=[cast(LangContentDict, c.model_dump()) for c in payload.contents], frequency=payload.frequency, status=payload.status, notification_id=payload.notification_id, diff --git a/api/services/billing_service.py b/api/services/billing_service.py index c3ce48b6bc..e096a0f2ba 100644 --- a/api/services/billing_service.py +++ b/api/services/billing_service.py @@ -37,6 +37,44 @@ class KnowledgeRateLimitDict(TypedDict): subscription_plan: str +class TenantFeaturePlanUsageDict(TypedDict): + result: str + history_id: str + + +class LangContentDict(TypedDict): + lang: str + title: str + subtitle: str + body: str + title_pic_url: str + + +class NotificationDict(TypedDict): + notification_id: str + contents: dict[str, LangContentDict] + frequency: Literal["once", "every_page_load"] + + +class AccountNotificationDict(TypedDict, total=False): + should_show: bool + notification: NotificationDict + shouldShow: bool + notifications: list[dict] + + +class UpsertNotificationDict(TypedDict): + notification_id: str + + +class BatchAddNotificationAccountsDict(TypedDict): + count: int + + +class DismissNotificationDict(TypedDict): + success: bool + + class BillingService: base_url = os.environ.get("BILLING_API_URL", "BILLING_API_URL") secret_key = os.environ.get("BILLING_API_SECRET_KEY", "BILLING_API_SECRET_KEY") @@ -94,7 +132,9 @@ class BillingService: return cls._send_request("GET", "/invoices", params=params) @classmethod - def update_tenant_feature_plan_usage(cls, tenant_id: str, feature_key: str, delta: int) -> dict: + def update_tenant_feature_plan_usage( + cls, tenant_id: str, feature_key: str, delta: int + ) -> TenantFeaturePlanUsageDict: """ Update tenant feature plan usage. @@ -114,7 +154,7 @@ class BillingService: ) @classmethod - def refund_tenant_feature_plan_usage(cls, history_id: str) -> dict: + def refund_tenant_feature_plan_usage(cls, history_id: str) -> TenantFeaturePlanUsageDict: """ Refund a previous usage charge. @@ -410,7 +450,7 @@ class BillingService: return tenant_whitelist @classmethod - def get_account_notification(cls, account_id: str) -> dict: + def get_account_notification(cls, account_id: str) -> AccountNotificationDict: """Return the active in-product notification for account_id, if any. Calling this endpoint also marks the notification as seen; subsequent @@ -434,13 +474,13 @@ class BillingService: @classmethod def upsert_notification( cls, - contents: list[dict], + contents: list[LangContentDict], frequency: str = "once", status: str = "active", notification_id: str | None = None, start_time: str | None = None, end_time: str | None = None, - ) -> dict: + ) -> UpsertNotificationDict: """Create or update a notification. contents: list of {"lang": str, "title": str, "subtitle": str, "body": str, "title_pic_url": str} @@ -461,7 +501,9 @@ class BillingService: return cls._send_request("POST", "/notifications", json=payload) @classmethod - def batch_add_notification_accounts(cls, notification_id: str, account_ids: list[str]) -> dict: + def batch_add_notification_accounts( + cls, notification_id: str, account_ids: list[str] + ) -> BatchAddNotificationAccountsDict: """Register target account IDs for a notification (max 1000 per call). Returns {"count": int}. @@ -473,7 +515,7 @@ class BillingService: ) @classmethod - def dismiss_notification(cls, notification_id: str, account_id: str) -> dict: + def dismiss_notification(cls, notification_id: str, account_id: str) -> DismissNotificationDict: """Mark a notification as dismissed for an account. Returns {"success": bool}. From e2ecd68556ff91585635e7bcbfce3b82b2b980c4 Mon Sep 17 00:00:00 2001 From: Renzo <170978465+RenzoMXD@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:56:19 -0500 Subject: [PATCH 07/11] refactor: migrate session.query to select API in rag pipeline task files (#34648) --- api/tasks/rag_pipeline/priority_rag_pipeline_run_task.py | 9 +++++---- api/tasks/rag_pipeline/rag_pipeline_run_task.py | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/api/tasks/rag_pipeline/priority_rag_pipeline_run_task.py b/api/tasks/rag_pipeline/priority_rag_pipeline_run_task.py index 3c5e152520..d8fa73b42d 100644 --- a/api/tasks/rag_pipeline/priority_rag_pipeline_run_task.py +++ b/api/tasks/rag_pipeline/priority_rag_pipeline_run_task.py @@ -10,6 +10,7 @@ from typing import Any import click from celery import shared_task # type: ignore from flask import current_app, g +from sqlalchemy import select from sqlalchemy.orm import Session, sessionmaker from configs import dify_config @@ -118,20 +119,20 @@ def run_single_rag_pipeline_task(rag_pipeline_invoke_entity: Mapping[str, Any], with Session(db.engine, expire_on_commit=False) as session: # Load required entities - account = session.query(Account).where(Account.id == user_id).first() + account = session.scalar(select(Account).where(Account.id == user_id).limit(1)) if not account: raise ValueError(f"Account {user_id} not found") - tenant = session.query(Tenant).where(Tenant.id == tenant_id).first() + tenant = session.scalar(select(Tenant).where(Tenant.id == tenant_id).limit(1)) if not tenant: raise ValueError(f"Tenant {tenant_id} not found") account.current_tenant = tenant - pipeline = session.query(Pipeline).where(Pipeline.id == pipeline_id).first() + pipeline = session.scalar(select(Pipeline).where(Pipeline.id == pipeline_id).limit(1)) if not pipeline: raise ValueError(f"Pipeline {pipeline_id} not found") - workflow = session.query(Workflow).where(Workflow.id == pipeline.workflow_id).first() + workflow = session.scalar(select(Workflow).where(Workflow.id == pipeline.workflow_id).limit(1)) if not workflow: raise ValueError(f"Workflow {pipeline.workflow_id} not found") diff --git a/api/tasks/rag_pipeline/rag_pipeline_run_task.py b/api/tasks/rag_pipeline/rag_pipeline_run_task.py index 52f66dddb8..db04b3375b 100644 --- a/api/tasks/rag_pipeline/rag_pipeline_run_task.py +++ b/api/tasks/rag_pipeline/rag_pipeline_run_task.py @@ -11,6 +11,7 @@ from typing import Any import click from celery import group, shared_task from flask import current_app, g +from sqlalchemy import select from sqlalchemy.orm import Session, sessionmaker from configs import dify_config @@ -132,20 +133,20 @@ def run_single_rag_pipeline_task(rag_pipeline_invoke_entity: Mapping[str, Any], with Session(db.engine) as session: # Load required entities - account = session.query(Account).where(Account.id == user_id).first() + account = session.scalar(select(Account).where(Account.id == user_id).limit(1)) if not account: raise ValueError(f"Account {user_id} not found") - tenant = session.query(Tenant).where(Tenant.id == tenant_id).first() + tenant = session.scalar(select(Tenant).where(Tenant.id == tenant_id).limit(1)) if not tenant: raise ValueError(f"Tenant {tenant_id} not found") account.current_tenant = tenant - pipeline = session.query(Pipeline).where(Pipeline.id == pipeline_id).first() + pipeline = session.scalar(select(Pipeline).where(Pipeline.id == pipeline_id).limit(1)) if not pipeline: raise ValueError(f"Pipeline {pipeline_id} not found") - workflow = session.query(Workflow).where(Workflow.id == pipeline.workflow_id).first() + workflow = session.scalar(select(Workflow).where(Workflow.id == pipeline.workflow_id).limit(1)) if not workflow: raise ValueError(f"Workflow {pipeline.workflow_id} not found") From 1261e5e5e8461d3b4754b8cf4c3f004def70db22 Mon Sep 17 00:00:00 2001 From: YBoy Date: Tue, 7 Apr 2026 07:57:02 +0200 Subject: [PATCH 08/11] refactor(api): type webhook validation result and workflow inputs with TypedDict (#34645) --- api/services/trigger/webhook_service.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/api/services/trigger/webhook_service.py b/api/services/trigger/webhook_service.py index e2d14c49e5..f72c69a33e 100644 --- a/api/services/trigger/webhook_service.py +++ b/api/services/trigger/webhook_service.py @@ -3,7 +3,7 @@ import logging import mimetypes import secrets from collections.abc import Callable, Mapping, Sequence -from typing import Any, TypedDict +from typing import Any, NotRequired, TypedDict import orjson from flask import request @@ -58,6 +58,18 @@ class RawWebhookDataDict(TypedDict): files: dict[str, Any] +class ValidationResultDict(TypedDict): + valid: bool + error: NotRequired[str] + + +class WorkflowInputsDict(TypedDict): + webhook_data: RawWebhookDataDict + webhook_headers: dict[str, str] + webhook_query_params: dict[str, str] + webhook_body: dict[str, Any] + + class WebhookService: """Service for handling webhook operations.""" @@ -173,7 +185,7 @@ class WebhookService: node_data = WebhookData.model_validate(node_config["data"], from_attributes=True) validation_result = cls._validate_http_metadata(raw_data, node_data) if not validation_result["valid"]: - raise ValueError(validation_result["error"]) + raise ValueError(validation_result.get("error", "Validation failed")) # Process and validate data according to configuration processed_data = cls._process_and_validate_data(raw_data, node_data) @@ -672,7 +684,7 @@ class WebhookService: raise ValueError(f"Required header missing: {header_name}") @classmethod - def _validate_http_metadata(cls, webhook_data: RawWebhookDataDict, node_data: WebhookData) -> dict[str, Any]: + def _validate_http_metadata(cls, webhook_data: RawWebhookDataDict, node_data: WebhookData) -> ValidationResultDict: """Validate HTTP method and content-type. Args: @@ -716,7 +728,7 @@ class WebhookService: return content_type.split(";")[0].strip() @classmethod - def _validation_error(cls, error_message: str) -> dict[str, Any]: + def _validation_error(cls, error_message: str) -> ValidationResultDict: """Create a standard validation error response. Args: @@ -737,7 +749,7 @@ class WebhookService: return False @classmethod - def build_workflow_inputs(cls, webhook_data: RawWebhookDataDict) -> dict[str, Any]: + def build_workflow_inputs(cls, webhook_data: RawWebhookDataDict) -> WorkflowInputsDict: """Construct workflow inputs payload from webhook data. Args: From c5a0bde3ecec31a6baf86b16ab61b8dcd31ef432 Mon Sep 17 00:00:00 2001 From: YBoy Date: Tue, 7 Apr 2026 07:57:22 +0200 Subject: [PATCH 09/11] refactor(api): type aliyun trace utils with TypedDict and tighten return types (#34642) --- api/core/ops/aliyun_trace/utils.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/api/core/ops/aliyun_trace/utils.py b/api/core/ops/aliyun_trace/utils.py index d8e105d6a3..aa35ac74c2 100644 --- a/api/core/ops/aliyun_trace/utils.py +++ b/api/core/ops/aliyun_trace/utils.py @@ -1,6 +1,6 @@ import json from collections.abc import Mapping -from typing import Any +from typing import Any, TypedDict from graphon.entities import WorkflowNodeExecution from graphon.enums import WorkflowNodeExecutionStatus @@ -56,10 +56,22 @@ def create_links_from_trace_id(trace_id: str | None) -> list[Link]: return links -def extract_retrieval_documents(documents: list[Document]) -> list[dict[str, Any]]: - documents_data = [] +class RetrievalDocumentMetadataDict(TypedDict): + dataset_id: Any + doc_id: Any + document_id: Any + + +class RetrievalDocumentDict(TypedDict): + content: str + metadata: RetrievalDocumentMetadataDict + score: Any + + +def extract_retrieval_documents(documents: list[Document]) -> list[RetrievalDocumentDict]: + documents_data: list[RetrievalDocumentDict] = [] for document in documents: - document_data = { + document_data: RetrievalDocumentDict = { "content": document.page_content, "metadata": { "dataset_id": document.metadata.get("dataset_id"), @@ -83,7 +95,7 @@ def create_common_span_attributes( framework: str = DEFAULT_FRAMEWORK_NAME, inputs: str = "", outputs: str = "", -) -> dict[str, Any]: +) -> dict[str, str]: return { GEN_AI_SESSION_ID: session_id, GEN_AI_USER_ID: user_id, From 19c80f0f0efd8b97fe1fd1ca41d0d5c3f8e97964 Mon Sep 17 00:00:00 2001 From: Statxc Date: Tue, 7 Apr 2026 02:57:42 -0300 Subject: [PATCH 10/11] refactor(api): type error stream response with TypedDict (#34641) --- api/core/app/apps/base_app_generate_response_converter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/core/app/apps/base_app_generate_response_converter.py b/api/core/app/apps/base_app_generate_response_converter.py index 66390116d4..6e5a86505c 100644 --- a/api/core/app/apps/base_app_generate_response_converter.py +++ b/api/core/app/apps/base_app_generate_response_converter.py @@ -107,13 +107,13 @@ class AppGenerateResponseConverter(ABC): return metadata @classmethod - def _error_to_stream_response(cls, e: Exception): + def _error_to_stream_response(cls, e: Exception) -> dict[str, Any]: """ Error to stream response. :param e: exception :return: """ - error_responses = { + error_responses: dict[type[Exception], dict[str, Any]] = { ValueError: {"code": "invalid_param", "status": 400}, ProviderTokenNotInitError: {"code": "provider_not_initialize", "status": 400}, QuotaExceededError: { @@ -127,7 +127,7 @@ class AppGenerateResponseConverter(ABC): } # Determine the response based on the type of exception - data = None + data: dict[str, Any] | None = None for k, v in error_responses.items(): if isinstance(e, k): data = v From 63db9a7a2f54cb483830980659a3f7cfa5faf976 Mon Sep 17 00:00:00 2001 From: Statxc Date: Tue, 7 Apr 2026 02:58:10 -0300 Subject: [PATCH 11/11] refactor(api): type load balancing config dicts with TypedDict (#34639) --- api/services/model_load_balancing_service.py | 28 ++++++++++++++++---- api/services/workflow_service.py | 4 +-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/api/services/model_load_balancing_service.py b/api/services/model_load_balancing_service.py index bc0bfd215c..3cce83a975 100644 --- a/api/services/model_load_balancing_service.py +++ b/api/services/model_load_balancing_service.py @@ -1,6 +1,6 @@ import json import logging -from typing import Any, Union +from typing import Any, TypedDict, Union from graphon.model_runtime.entities.model_entities import ModelType from graphon.model_runtime.entities.provider_entities import ( @@ -25,6 +25,23 @@ from models.provider import LoadBalancingModelConfig, ProviderCredential, Provid logger = logging.getLogger(__name__) +class LoadBalancingConfigDetailDict(TypedDict): + id: str + name: str + credentials: dict[str, Any] + enabled: bool + + +class LoadBalancingConfigSummaryDict(TypedDict): + id: str + name: str + credentials: dict[str, Any] + credential_id: str | None + enabled: bool + in_cooldown: bool + ttl: int + + class ModelLoadBalancingService: @staticmethod def _get_provider_manager(tenant_id: str) -> ProviderManager: @@ -74,7 +91,7 @@ class ModelLoadBalancingService: def get_load_balancing_configs( self, tenant_id: str, provider: str, model: str, model_type: str, config_from: str = "" - ) -> tuple[bool, list[dict]]: + ) -> tuple[bool, list[LoadBalancingConfigSummaryDict]]: """ Get load balancing configurations. :param tenant_id: workspace id @@ -156,7 +173,7 @@ class ModelLoadBalancingService: decoding_rsa_key, decoding_cipher_rsa = encrypter.get_decrypt_decoding(tenant_id) # fetch status and ttl for each config - datas = [] + datas: list[LoadBalancingConfigSummaryDict] = [] for load_balancing_config in load_balancing_configs: in_cooldown, ttl = LBModelManager.get_config_in_cooldown_and_ttl( tenant_id=tenant_id, @@ -214,7 +231,7 @@ class ModelLoadBalancingService: def get_load_balancing_config( self, tenant_id: str, provider: str, model: str, model_type: str, config_id: str - ) -> dict | None: + ) -> LoadBalancingConfigDetailDict | None: """ Get load balancing configuration. :param tenant_id: workspace id @@ -267,12 +284,13 @@ class ModelLoadBalancingService: credentials=credentials, credential_form_schemas=credential_schemas.credential_form_schemas ) - return { + result: LoadBalancingConfigDetailDict = { "id": load_balancing_model_config.id, "name": load_balancing_model_config.name, "credentials": credentials, "enabled": load_balancing_model_config.enabled, } + return result def _init_inherit_config( self, tenant_id: str, provider: str, model: str, model_type: ModelType diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 662e0410f9..eeb795e5a8 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -635,7 +635,7 @@ class WorkflowService: # If we can't determine the status, assume load balancing is not enabled return False - def _get_load_balancing_configs(self, tenant_id: str, provider: str, model_name: str) -> list[dict]: + def _get_load_balancing_configs(self, tenant_id: str, provider: str, model_name: str) -> list[dict[str, Any]]: """ Get all load balancing configurations for a model. @@ -659,7 +659,7 @@ class WorkflowService: _, custom_configs = model_load_balancing_service.get_load_balancing_configs( tenant_id=tenant_id, provider=provider, model=model_name, model_type="llm", config_from="custom-model" ) - all_configs = configs + custom_configs + all_configs = cast(list[dict[str, Any]], configs) + cast(list[dict[str, Any]], custom_configs) return [config for config in all_configs if config.get("credential_id")]