diff --git a/api/core/helper/position_helper.py b/api/core/helper/position_helper.py index 8def6fe4ed..314f052832 100644 --- a/api/core/helper/position_helper.py +++ b/api/core/helper/position_helper.py @@ -1,7 +1,7 @@ import os from collections import OrderedDict from collections.abc import Callable -from typing import Any +from typing import TypeVar from configs import dify_config from core.tools.utils.yaml_utils import load_yaml_file @@ -72,11 +72,14 @@ def pin_position_map(original_position_map: dict[str, int], pin_list: list[str]) return position_map +T = TypeVar("T") + + def is_filtered( include_set: set[str], exclude_set: set[str], - data: Any, - name_func: Callable[[Any], str], + data: T, + name_func: Callable[[T], str], ) -> bool: """ Check if the object should be filtered out. @@ -103,9 +106,9 @@ def is_filtered( def sort_by_position_map( position_map: dict[str, int], - data: list[Any], - name_func: Callable[[Any], str], -) -> list[Any]: + data: list[T], + name_func: Callable[[T], str], +): """ Sort the objects by the position map. If the name of the object is not in the position map, it will be put at the end. @@ -122,9 +125,9 @@ def sort_by_position_map( def sort_to_dict_by_position_map( position_map: dict[str, int], - data: list[Any], - name_func: Callable[[Any], str], -) -> OrderedDict[str, Any]: + data: list[T], + name_func: Callable[[T], str], +): """ Sort the objects into a ordered dict by the position map. If the name of the object is not in the position map, it will be put at the end. @@ -134,4 +137,4 @@ def sort_to_dict_by_position_map( :return: an OrderedDict with the sorted pairs of name and object """ sorted_items = sort_by_position_map(position_map, data, name_func) - return OrderedDict([(name_func(item), item) for item in sorted_items]) + return OrderedDict((name_func(item), item) for item in sorted_items) diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index e856e6a88a..cc5e46deeb 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -52,6 +52,10 @@ const ChatWrapper = () => { allInputsHidden, initUserVariables, } = useChatWithHistoryContext() + + // Semantic variable for better code readability + const isHistoryConversation = !!currentConversationId + const appConfig = useMemo(() => { const config = appParams || {} @@ -62,9 +66,9 @@ const ChatWrapper = () => { fileUploadConfig: (config as any).system_parameters, }, supportFeedback: true, - opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement, + opening_statement: isHistoryConversation ? currentConversationItem?.introduction : (config as any).opening_statement, } as ChatConfig - }, [appParams, currentConversationItem?.introduction, currentConversationId]) + }, [appParams, currentConversationItem?.introduction, isHistoryConversation]) const { chatList, setTargetMessageId, @@ -75,7 +79,7 @@ const ChatWrapper = () => { } = useChat( appConfig, { - inputs: (currentConversationId ? currentConversationInputs : newConversationInputs) as any, + inputs: (isHistoryConversation ? currentConversationInputs : newConversationInputs) as any, inputsForm: inputsForms, }, appPrevChatTree, @@ -83,7 +87,7 @@ const ChatWrapper = () => { clearChatList, setClearChatList, ) - const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current + const inputsFormValue = isHistoryConversation ? currentConversationInputs : newConversationInputsRef?.current const inputDisabled = useMemo(() => { if (allInputsHidden) return false @@ -132,7 +136,7 @@ const ChatWrapper = () => { const data: any = { query: message, files, - inputs: formatBooleanInputs(inputsForms, currentConversationId ? currentConversationInputs : newConversationInputs), + inputs: formatBooleanInputs(inputsForms, isHistoryConversation ? currentConversationInputs : newConversationInputs), conversation_id: currentConversationId, parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null, } @@ -142,11 +146,11 @@ const ChatWrapper = () => { data, { onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId), - onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted, + onConversationComplete: isHistoryConversation ? undefined : handleNewConversationCompleted, isPublicAPI: !isInstalledApp, }, ) - }, [chatList, handleNewConversationCompleted, handleSend, currentConversationId, currentConversationInputs, newConversationInputs, isInstalledApp, appId]) + }, [chatList, handleNewConversationCompleted, handleSend, isHistoryConversation, currentConversationInputs, newConversationInputs, isInstalledApp, appId]) const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => { const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)! @@ -159,31 +163,30 @@ const ChatWrapper = () => { }, [chatList, doSend]) const messageList = useMemo(() => { - if (currentConversationId) - return chatList + // Always filter out opening statement from message list as it's handled separately in welcome component return chatList.filter(item => !item.isOpeningStatement) - }, [chatList, currentConversationId]) + }, [chatList]) - const [collapsed, setCollapsed] = useState(!!currentConversationId) + const [collapsed, setCollapsed] = useState(isHistoryConversation) const chatNode = useMemo(() => { if (allInputsHidden || !inputsForms.length) return null if (isMobile) { - if (!currentConversationId) + if (!isHistoryConversation) return return null } else { return } - }, [inputsForms.length, isMobile, currentConversationId, collapsed, allInputsHidden]) + }, [inputsForms.length, isMobile, isHistoryConversation, collapsed, allInputsHidden]) const welcome = useMemo(() => { const welcomeMessage = chatList.find(item => item.isOpeningStatement) if (respondingState) return null - if (currentConversationId) + if (isHistoryConversation) return null if (!welcomeMessage) return null @@ -224,7 +227,7 @@ const ChatWrapper = () => { ) - }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden]) + }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, isHistoryConversation, inputsForms.length, respondingState, allInputsHidden]) const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon) ? { chatFooterClassName='pb-4' chatFooterInnerClassName={`mx-auto w-full max-w-[768px] ${isMobile ? 'px-2' : 'px-4'}`} onSend={doSend} - inputs={currentConversationId ? currentConversationInputs as any : newConversationInputs} + inputs={isHistoryConversation ? currentConversationInputs as any : newConversationInputs} inputsForm={inputsForms} onRegenerate={doRegenerate} onStopResponding={handleStop} diff --git a/web/i18n/de-DE/common.ts b/web/i18n/de-DE/common.ts index 70f3d97037..dec18fd4f2 100644 --- a/web/i18n/de-DE/common.ts +++ b/web/i18n/de-DE/common.ts @@ -490,6 +490,7 @@ const translation = { providerManagedTip: 'Die aktuelle Konfiguration wird vom Anbieter gehostet.', configLoadBalancing: 'Konfiguration Lastenverteilung', specifyModelCredentialTip: 'Verwenden Sie ein konfiguriertes Modellzugang.', + manageCredentials: 'Anmeldeinformationen verwalten', }, }, dataSource: { diff --git a/web/i18n/fa-IR/common.ts b/web/i18n/fa-IR/common.ts index 84bda8dfa5..d78dd45662 100644 --- a/web/i18n/fa-IR/common.ts +++ b/web/i18n/fa-IR/common.ts @@ -494,6 +494,7 @@ const translation = { specifyModelCredentialTip: 'از اعتبارنامه مدل پیکربندی شده استفاده کنید.', providerManagedTip: 'تنظیمات فعلی توسط ارائه‌دهنده میزبانی می‌شود.', modelCredentials: 'مدل اعتبارنامه', + manageCredentials: 'مدیریت اعتبارنامه ها', }, }, dataSource: { diff --git a/web/i18n/hi-IN/common.ts b/web/i18n/hi-IN/common.ts index a2de883cd9..ade633dec5 100644 --- a/web/i18n/hi-IN/common.ts +++ b/web/i18n/hi-IN/common.ts @@ -510,6 +510,7 @@ const translation = { specifyModelCredential: 'मॉडल की क्रेडेंशियल निर्दिष्ट करें', specifyModelCredentialTip: 'कॉन्फ़िगर की गई मॉडल क्रेडेंशियल का उपयोग करें।', providerManagedTip: 'वर्तमान कॉन्फ़िगरेशन प्रदाता द्वारा होस्ट किया गया है।', + selectModelCredential: 'एक मॉडल क्रेडेंशियल चुनें', }, }, dataSource: { diff --git a/web/i18n/ro-RO/common.ts b/web/i18n/ro-RO/common.ts index 5fa8a9e2b2..eea41d1c0e 100644 --- a/web/i18n/ro-RO/common.ts +++ b/web/i18n/ro-RO/common.ts @@ -490,6 +490,7 @@ const translation = { providerManagedTip: 'Configurarea curentă este găzduită de furnizor.', modelCredentials: 'Credențiale model', specifyModelCredentialTip: 'Utilizați un acreditiv de model configurat.', + addNewModelCredential: 'Adăugați acreditive noi pentru model', }, }, dataSource: { diff --git a/web/i18n/th-TH/common.ts b/web/i18n/th-TH/common.ts index db30f103b3..b78ad47f22 100644 --- a/web/i18n/th-TH/common.ts +++ b/web/i18n/th-TH/common.ts @@ -489,6 +489,7 @@ const translation = { authorizationError: 'ข้อผิดพลาดในการอนุญาต', specifyModelCredentialTip: 'ใช้ข้อมูลรับรองโมเดลที่กำหนดไว้', providerManagedTip: 'การกำหนดค่าปัจจุบันถูกโฮสต์โดยผู้ให้บริการ.', + customModelCredentialsDeleteTip: 'ข้อมูลรับรองกำลังถูกใช้งานและไม่สามารถลบได้', }, }, dataSource: {