From 6c315f63230c2b98c569e295730deb97fca70d44 Mon Sep 17 00:00:00 2001 From: Armen Derikyan <82438895+Derikyan@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:20:22 +0400 Subject: [PATCH 1/9] fix(chat): disable fields after editing shared app (Issue #3017) (#3033) --- .../components/Common/ApplicationWizard/ApplicationWizard.tsx | 2 -- .../Common/ApplicationWizard/CodeAppView/CodeAppView.tsx | 3 ++- .../src/components/Common/ApplicationWizard/CustomAppView.tsx | 3 ++- .../src/components/Common/ApplicationWizard/QuickAppView.tsx | 3 ++- .../chat/src/components/Common/ApplicationWizard/view-props.ts | 1 - 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx b/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx index 5d8dd95f76..8c9a19a71c 100644 --- a/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx +++ b/apps/chat/src/components/Common/ApplicationWizard/ApplicationWizard.tsx @@ -41,7 +41,6 @@ export const ApplicationWizard: React.FC = ({ const selectedApplication = useAppSelector( ApplicationSelectors.selectApplicationDetail, ); - const isSharedWithMe = selectedApplication?.sharedWithMe; const handleClose = useCallback( (result?: boolean) => { @@ -105,7 +104,6 @@ export const ApplicationWizard: React.FC = ({ isEdit={isEdit} currentReference={currentReference} selectedApplication={isEdit ? selectedApplication : undefined} - isSharedWithMe={!!isSharedWithMe} /> )} diff --git a/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx b/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx index f59046111c..faff1a05be 100644 --- a/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx +++ b/apps/chat/src/components/Common/ApplicationWizard/CodeAppView/CodeAppView.tsx @@ -74,7 +74,6 @@ export const CodeAppView: FC = ({ type, selectedApplication, currentReference, - isSharedWithMe, }) => { const { t } = useTranslation(Translation.Chat); @@ -90,6 +89,8 @@ export const CodeAppView: FC = ({ selectedApplication && isApplicationDeployed(selectedApplication); const isCodeEditorDirty = useAppSelector(CodeEditorSelectors.selectIsDirty); + const isSharedWithMe = selectedApplication?.sharedWithMe; + const [editorConfirmation, setEditorConfirmation] = useState(); useEffect(() => { diff --git a/apps/chat/src/components/Common/ApplicationWizard/CustomAppView.tsx b/apps/chat/src/components/Common/ApplicationWizard/CustomAppView.tsx index 39a1e05434..7e257be504 100644 --- a/apps/chat/src/components/Common/ApplicationWizard/CustomAppView.tsx +++ b/apps/chat/src/components/Common/ApplicationWizard/CustomAppView.tsx @@ -49,7 +49,6 @@ export const CustomAppView: React.FC = ({ type, currentReference, selectedApplication, - isSharedWithMe, }) => { const { t } = useTranslation(Translation.Chat); @@ -59,6 +58,8 @@ export const CustomAppView: React.FC = ({ const topics = useAppSelector(SettingsSelectors.selectTopics); const models = useAppSelector(ModelsSelectors.selectModels); + const isSharedWithMe = selectedApplication?.sharedWithMe; + const modelsWithFolderId = models.map((model) => ({ ...model, folderId: '', diff --git a/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx b/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx index d1ff9b67da..a0c43d018a 100644 --- a/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx +++ b/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx @@ -49,7 +49,6 @@ export const QuickAppView: React.FC = ({ type, currentReference, selectedApplication, - isSharedWithMe, }) => { const { t } = useTranslation(Translation.Chat); @@ -60,6 +59,8 @@ export const QuickAppView: React.FC = ({ const topics = useAppSelector(SettingsSelectors.selectTopics); const models = useAppSelector(ModelsSelectors.selectModels); + const isSharedWithMe = selectedApplication?.sharedWithMe; + const modelsWithFolderId = models.map((model) => ({ ...model, folderId: '', diff --git a/apps/chat/src/components/Common/ApplicationWizard/view-props.ts b/apps/chat/src/components/Common/ApplicationWizard/view-props.ts index c005c9c626..8dc090d103 100644 --- a/apps/chat/src/components/Common/ApplicationWizard/view-props.ts +++ b/apps/chat/src/components/Common/ApplicationWizard/view-props.ts @@ -10,5 +10,4 @@ export interface ViewProps { isEdit?: boolean; currentReference?: string; selectedApplication?: CustomApplicationModel; - isSharedWithMe?: boolean; } From e2c634558f93d3215e4618bc867449171895e0fe Mon Sep 17 00:00:00 2001 From: Gimir Date: Wed, 29 Jan 2025 14:10:50 +0300 Subject: [PATCH 2/9] fix(chat): fix custom buttons in playback mode, hide templates in schema chats (Issue #3002, #3013) (#3035) Co-authored-by: Magomed-Elbi Dzhukalaev --- apps/chat/src/components/Chat/Chat.tsx | 2 +- .../ChatMessageContent/UserMessage.tsx | 1 + .../Chat/ChatMessage/MessageButtons.tsx | 12 +++++- .../chat/src/components/Chat/ChatStarters.tsx | 4 ++ .../Chat/Playback/PlaybackControls.tsx | 38 ++++++++++++++++--- .../conversations/conversations.selectors.ts | 10 ++++- 6 files changed, 58 insertions(+), 9 deletions(-) diff --git a/apps/chat/src/components/Chat/Chat.tsx b/apps/chat/src/components/Chat/Chat.tsx index a2e197c1c4..f0dfc3d26f 100644 --- a/apps/chat/src/components/Chat/Chat.tsx +++ b/apps/chat/src/components/Chat/Chat.tsx @@ -755,7 +755,7 @@ export const ChatView = memo(() => { /> )} - {!isPlayback && } + {!isPlayback && ( {!isMessageStreaming && ( <> - {isEditTemplatesAvailable && ( + {isEditTemplatesAvailable && !isConversationsWithSchema && ( { const { t } = useTranslation(Translation.Chat); + const isConversationsWithSchema = useAppSelector( + ConversationsSelectors.selectIsSelectedConversationsWithSchema, + ); + const isAssistant = message.role === Role.Assistant; if (isAssistant) { @@ -389,7 +397,7 @@ export const MessageMobileButtons = ({ !isMessageStreaming && !isConversationInvalid && ( <> - {isEditTemplatesAvailable && ( + {isEditTemplatesAvailable && !isConversationsWithSchema && ( onToggleTemplatesEditing()} diff --git a/apps/chat/src/components/Chat/ChatStarters.tsx b/apps/chat/src/components/Chat/ChatStarters.tsx index 9bf6236423..ea633a53cc 100644 --- a/apps/chat/src/components/Chat/ChatStarters.tsx +++ b/apps/chat/src/components/Chat/ChatStarters.tsx @@ -23,6 +23,9 @@ const ChatStartersView = ({ schema }: ChatStartersViewProps) => { const dispatch = useAppDispatch(); const formValue = useAppSelector(ChatSelectors.selectChatFormValue); + const isPlayback = useAppSelector( + ConversationsSelectors.selectIsPlaybackSelectedConversations, + ); const handleChange = useCallback( (property: string, value: MessageFormValueType, submit?: boolean) => { @@ -56,6 +59,7 @@ const ChatStartersView = ({ schema }: ChatStartersViewProps) => { buttonsWrapperClassName="md:justify-center flex-nowrap overflow-x-auto overflow-y-hidden px-2" buttonClassName="shrink-0" propertyWrapperClassName="items-center" + disabled={isPlayback} /> ); }; diff --git a/apps/chat/src/components/Chat/Playback/PlaybackControls.tsx b/apps/chat/src/components/Chat/Playback/PlaybackControls.tsx index 59762260c2..db3632252c 100644 --- a/apps/chat/src/components/Chat/Playback/PlaybackControls.tsx +++ b/apps/chat/src/components/Chat/Playback/PlaybackControls.tsx @@ -12,8 +12,13 @@ import { useTranslation } from 'next-i18next'; import classNames from 'classnames'; +import { + getConfigurationValue, + getMessageFormValue, +} from '@/src/utils/app/form-schema'; import { hasParentWithFloatingOverlay } from '@/src/utils/app/modals'; +import { ChatActions } from '@/src/store/chat/chat.reducer'; import { ConversationsActions, ConversationsSelectors, @@ -26,6 +31,8 @@ import { ScrollDownButton } from '@/src/components/Common/ScrollDownButton'; import { ChatInputFooter } from '../ChatInput/ChatInputFooter'; import { PlaybackAttachments } from './PlaybackAttachments'; +import { Attachment } from '@epam/ai-dial-shared'; + interface Props { showScrollDownButton: boolean; onScrollDownClick: () => void; @@ -105,17 +112,21 @@ export const PlaybackControls = ({ currentMessage && currentMessage?.custom_content?.attachments?.length ? currentMessage.custom_content.attachments : []; + const form_value = + getMessageFormValue(currentMessage) ?? + getConfigurationValue(currentMessage); const message = attachments.length - ? { content, custom_content: { attachments } } - : { content }; + ? { content, custom_content: { attachments, form_value } } + : { content, custom_content: { form_value } }; return message; }, [activeIndex, isActiveIndex, isNextMessageInStack, selectedConversations]); - const hasAttachments = + const hasAttachments = !!( activeMessage && activeMessage.custom_content && activeMessage.custom_content.attachments && - activeMessage.custom_content.attachments.length; + activeMessage.custom_content.attachments.length + ); const handlePlayNextMessage = useCallback(() => { if (isMessageStreaming || !isNextMessageInStack) { @@ -209,6 +220,21 @@ export const PlaybackControls = ({ }; }, [controlsContainerRef, onResize]); + useEffect(() => { + if ( + phase === PlaybackPhases.MESSAGE && + activeMessage?.custom_content?.form_value + ) { + Object.entries(activeMessage.custom_content.form_value).forEach( + ([property, value]) => { + dispatch(ChatActions.setFormValue({ property, value })); + }, + ); + } else if (phase === PlaybackPhases.EMPTY) { + dispatch(ChatActions.resetFormValue()); + } + }, [activeMessage?.custom_content?.form_value, dispatch, phase]); + return (
)}
+ {confirmSharingRevoke && selectedApplication && ( + { + if (result) { + dispatch( + ShareActions.revokeAccess({ + resourceId: selectedApplication.id, + featureType: FeatureType.Application, + }), + ); + + handleEdit(confirmSharingRevoke.data); + setConfirmSharingRevoke(undefined); + } + }} + /> + )} + SettingsSelectors.isFeatureEnabled(state, Feature.Marketplace), ); From e555aba6ce818f6f6dd62429ff0e77bc8e9d7bc2 Mon Sep 17 00:00:00 2001 From: Gimir Date: Wed, 29 Jan 2025 21:05:45 +0300 Subject: [PATCH 6/9] feat(chat): add document_relative_url and model fields to quick app editor (Issue #3053) (#3057) Co-authored-by: Magomed-Elbi Dzhukalaev --- .../src/testData/expectedConstants.ts | 2 +- .../AssistantSubModelSelector.tsx | 94 +-------------- .../Common/ApplicationWizard/QuickAppView.tsx | 36 ++++++ .../Common/ApplicationWizard/form.ts | 108 ++++++++++-------- .../src/components/Common/ModelsSelector.tsx | 102 +++++++++++++++++ .../src/components/Files/FileManagerModal.tsx | 2 +- .../src/components/Files/PreUploadModal.tsx | 2 +- apps/chat/src/types/quick-apps.ts | 1 + apps/chat/src/utils/app/application.ts | 42 ++++++- 9 files changed, 248 insertions(+), 141 deletions(-) create mode 100644 apps/chat/src/components/Common/ModelsSelector.tsx diff --git a/apps/chat-e2e/src/testData/expectedConstants.ts b/apps/chat-e2e/src/testData/expectedConstants.ts index 8846654e91..d534495691 100644 --- a/apps/chat-e2e/src/testData/expectedConstants.ts +++ b/apps/chat-e2e/src/testData/expectedConstants.ts @@ -152,7 +152,7 @@ export const ExpectedConstants = { controlChars: `\b\t \f`, hieroglyphChars: `あおㅁㄹñ¿äß맞습니다. 한국어 학습의 인기는 그 나라의 문화와 경제뿐만 아니라 언어 자체의 매력에서도 비롯됩니다. 한국어는 한글이라는 고유한 문자 시스템을 사용하는데, 이는 15세기에 세종대왕에 의해 창안되었습니다. 한글은 그 논리적이고 과학적인 설계로 인해 배우기 쉬운 것으로 여겨지며, 이 또`, attachedFileError: (filename: string) => - `You've trying to upload files with incorrect type: ${filename}`, + `You're trying to upload files with incorrect type: ${filename}`, allowedSpecialChars: "(`~!@#$^*-_+[]'|<>.?)", allowedSpecialSymbolsInName: () => `Test ${ExpectedConstants.allowedSpecialChars}`, diff --git a/apps/chat/src/components/Chat/ChatSettings/AssistantSubModelSelector.tsx b/apps/chat/src/components/Chat/ChatSettings/AssistantSubModelSelector.tsx index 3a80e1372b..d09aac7bb0 100644 --- a/apps/chat/src/components/Chat/ChatSettings/AssistantSubModelSelector.tsx +++ b/apps/chat/src/components/Chat/ChatSettings/AssistantSubModelSelector.tsx @@ -1,59 +1,8 @@ -import { useMemo } from 'react'; - import { useTranslation } from 'next-i18next'; -import classNames from 'classnames'; - -import { getOpenAIEntityFullName } from '@/src/utils/app/conversation'; - -import { EntityType } from '@/src/types/common'; -import { DialAIEntityModel } from '@/src/types/models'; import { Translation } from '@/src/types/translation'; -import { useAppSelector } from '@/src/store/hooks'; -import { ModelsSelectors } from '@/src/store/models/models.reducers'; - -import { ModelIcon } from '../../Chatbar/ModelIcon'; -import { Combobox } from '../../Common/Combobox'; -import { DisableOverlay } from '../../Common/DisableOverlay'; -import { EntityMarkdownDescription } from '../../Common/MarkdownDescription'; - -interface ModelSelectRowProps { - item: DialAIEntityModel; - isNotAllowed: boolean; -} - -const ModelSelectRow = ({ item, isNotAllowed }: ModelSelectRowProps) => { - const { t } = useTranslation(Translation.Chat); - - return ( -
- -
- - {getOpenAIEntityFullName(item)} - {item.version && ( - {item.version} - )} - - {isNotAllowed && ( - - - {t('chat.error.incorrect-selected', { - context: EntityType.Model, - })} - - - )} -
-
- ); -}; +import { ModelsSelector } from '@/src/components/Common/ModelsSelector'; interface Props { assistantModelId: string; @@ -68,45 +17,14 @@ export const AssistantSubModelSelector = ({ }: Props) => { const { t } = useTranslation(Translation.Chat); - const onlyModels = useAppSelector(ModelsSelectors.selectModelsOnly); - const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap); - - const assistantSubModel = useMemo( - () => modelsMap[assistantModelId], - [assistantModelId, modelsMap], - ); - return ( <> -
- {disabled && } - - getOpenAIEntityFullName(model) - } - getItemValue={(model: DialAIEntityModel) => model.id} - itemRow={({ item }) => ( - - )} - onSelectItem={(itemID: string) => { - onSelectAssistantSubModel(itemID); - }} - /> -
+ ); }; diff --git a/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx b/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx index a0c43d018a..8fd85b7108 100644 --- a/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx +++ b/apps/chat/src/components/Common/ApplicationWizard/QuickAppView.tsx @@ -27,6 +27,7 @@ import { Field } from '@/src/components/Common/Forms/Field'; import { withErrorMessage } from '@/src/components/Common/Forms/FieldErrorMessage'; import { FieldTextArea } from '@/src/components/Common/Forms/FieldTextArea'; import { withLabel } from '@/src/components/Common/Forms/Label'; +import { ModelsSelector } from '@/src/components/Common/ModelsSelector'; import { CustomLogoSelect } from '@/src/components/Settings/CustomLogoSelect'; import { @@ -42,6 +43,7 @@ const TopicsSelector = withLabel(DropdownSelector); const ToolsetEditor = withErrorMessage(withLabel(Editor)); const Slider = withLabel(TemperatureSlider, true); const ControlledField = withController(Field); +const ModelsSelectorField = withErrorMessage(withLabel(ModelsSelector)); export const QuickAppView: React.FC = ({ onClose, @@ -166,6 +168,26 @@ export const QuickAppView: React.FC = ({ )} /> + ( + field.onChange(getLogoId(v))} + onDeleteLocalLogoHandler={() => field.onChange('')} + customPlaceholder={t('No document relative url')} + className="max-w-full" + fileManagerModalTitle="Select document" + error={errors.documentRelativeUrl?.message} + allowedTypes={['*/*']} + disabled={isSharedWithMe} + tooltip={isSharedWithMe ? getSharedTooltip('file') : ''} + /> + )} + /> + = ({ )} /> + ( + + )} + /> + > = Omit< @@ -304,53 +307,62 @@ export const getDefaultValues = ({ app?: CustomApplicationModel; models?: ShareEntity[]; runtime?: string; -}): FormData => ({ - name: - app?.name ?? - getNextDefaultName(DEFAULT_APPLICATION_NAME, models ?? [], 0, true), - description: app ? getModelDescription(app) : '', - version: app?.version ?? DEFAULT_VERSION, - iconUrl: app?.iconUrl ?? '', - topics: app?.topics ?? [], - inputAttachmentTypes: app?.inputAttachmentTypes ?? [], - maxInputAttachments: String(app?.maxInputAttachments ?? ''), - completionUrl: app?.completionUrl ?? '', - features: safeStringify(app?.features), - instructions: app ? getQuickAppConfig(app).instructions : '', - temperature: app ? getQuickAppConfig(app).temperature : DEFAULT_TEMPERATURE, - toolset: app ? getToolsetStr(getQuickAppConfig(app)) : '', - sources: app?.function?.sourceFolder ?? '', - endpoints: app?.function?.mapping - ? Object.entries(app.function.mapping).map(([key, value]) => ({ - label: key, - visibleName: FEATURES_ENDPOINTS_NAMES[key], - value, - editableKey: - !FEATURES_ENDPOINTS[key as keyof typeof FEATURES_ENDPOINTS], - static: key === FEATURES_ENDPOINTS.chat_completion, - })) - : [ - { - label: FEATURES_ENDPOINTS.chat_completion, - visibleName: - FEATURES_ENDPOINTS_NAMES[FEATURES_ENDPOINTS.chat_completion], - value: - FEATURES_ENDPOINTS_DEFAULT_VALUES[ - FEATURES_ENDPOINTS.chat_completion - ] || '', - editableKey: false, - static: true, - }, - ], - env: app?.function?.env - ? Object.entries(app.function.env).map(([label, value]) => ({ - label, - value, - editableKey: true, - })) - : [], - runtime: app?.function?.runtime ?? runtime ?? 'python3.11', -}); +}): FormData => { + const quickAppConfig = app ? getQuickAppConfig(app) : undefined; + + return { + name: + app?.name ?? + getNextDefaultName(DEFAULT_APPLICATION_NAME, models ?? [], 0, true), + description: app ? getModelDescription(app) : '', + version: app?.version ?? DEFAULT_VERSION, + iconUrl: app?.iconUrl ?? '', + topics: app?.topics ?? [], + inputAttachmentTypes: app?.inputAttachmentTypes ?? [], + maxInputAttachments: String(app?.maxInputAttachments ?? ''), + completionUrl: app?.completionUrl ?? '', + features: safeStringify(app?.features), + sources: app?.function?.sourceFolder ?? '', + endpoints: app?.function?.mapping + ? Object.entries(app.function.mapping).map(([key, value]) => ({ + label: key, + visibleName: FEATURES_ENDPOINTS_NAMES[key], + value, + editableKey: + !FEATURES_ENDPOINTS[key as keyof typeof FEATURES_ENDPOINTS], + static: key === FEATURES_ENDPOINTS.chat_completion, + })) + : [ + { + label: FEATURES_ENDPOINTS.chat_completion, + visibleName: + FEATURES_ENDPOINTS_NAMES[FEATURES_ENDPOINTS.chat_completion], + value: + FEATURES_ENDPOINTS_DEFAULT_VALUES[ + FEATURES_ENDPOINTS.chat_completion + ] || '', + editableKey: false, + static: true, + }, + ], + env: app?.function?.env + ? Object.entries(app.function.env).map(([label, value]) => ({ + label, + value, + editableKey: true, + })) + : [], + runtime: app?.function?.runtime ?? runtime ?? 'python3.11', + // QUICK APP + instructions: quickAppConfig?.instructions ?? '', + temperature: quickAppConfig?.temperature ?? DEFAULT_TEMPERATURE, + toolset: quickAppConfig ? getToolsetStr(quickAppConfig) : '', + model: + quickAppConfig?.model ?? + DefaultsService.get('quickAppsModel', DEFAULT_QUICK_APPS_MODEL), + documentRelativeUrl: quickAppConfig?.document_relative_url ?? '', + }; +}; export const getApplicationData = ( formData: FormData, @@ -385,6 +397,8 @@ export const getApplicationData = ( config: formData.toolset, instructions: formData.instructions ?? '', temperature: formData.temperature, + model: formData.model, + document_relative_url: formData.documentRelativeUrl, }); preparedData.completionUrl = constructPath( DefaultsService.get('quickAppsHost', DEFAULT_QUICK_APPS_HOST), diff --git a/apps/chat/src/components/Common/ModelsSelector.tsx b/apps/chat/src/components/Common/ModelsSelector.tsx new file mode 100644 index 0000000000..cdc87f8c29 --- /dev/null +++ b/apps/chat/src/components/Common/ModelsSelector.tsx @@ -0,0 +1,102 @@ +import { memo, useMemo } from 'react'; + +import { useTranslation } from 'next-i18next'; + +import classNames from 'classnames'; + +import { getOpenAIEntityFullName } from '@/src/utils/app/conversation'; + +import { EntityType } from '@/src/types/common'; +import { DialAIEntityModel } from '@/src/types/models'; +import { Translation } from '@/src/types/translation'; + +import { useAppSelector } from '@/src/store/hooks'; +import { ModelsSelectors } from '@/src/store/models/models.reducers'; + +import { ModelIcon } from '@/src/components/Chatbar/ModelIcon'; +import { Combobox } from '@/src/components/Common/Combobox'; +import { DisableOverlay } from '@/src/components/Common/DisableOverlay'; +import { EntityMarkdownDescription } from '@/src/components/Common/MarkdownDescription'; + +interface ModelSelectRowProps { + item: DialAIEntityModel; + isNotAllowed: boolean; +} + +const ModelSelectRow = ({ item, isNotAllowed }: ModelSelectRowProps) => { + const { t } = useTranslation(Translation.Chat); + + return ( +
+ +
+ + {getOpenAIEntityFullName(item)} + {item.version && ( + {item.version} + )} + + {isNotAllowed && ( + + + {t('chat.error.incorrect-selected', { + context: EntityType.Model, + })} + + + )} +
+
+ ); +}; + +interface ModelsSelectorProps { + value: string; + onChange: (modelId: string) => void; + disabled?: boolean; +} + +export const ModelsSelector = memo(function ModelsSelector({ + value, + onChange, + disabled, +}: ModelsSelectorProps) { + const onlyModels = useAppSelector(ModelsSelectors.selectModelsOnly); + const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap); + + const model = useMemo(() => modelsMap[value], [value, modelsMap]); + + return ( +
+ {disabled && } + + getOpenAIEntityFullName(model) + } + getItemValue={(model: DialAIEntityModel) => model.id} + itemRow={({ item }) => ( + + )} + onSelectItem={onChange} + /> +
+ ); +}); diff --git a/apps/chat/src/components/Files/FileManagerModal.tsx b/apps/chat/src/components/Files/FileManagerModal.tsx index 28f62617e7..6208d46ba5 100644 --- a/apps/chat/src/components/Files/FileManagerModal.tsx +++ b/apps/chat/src/components/Files/FileManagerModal.tsx @@ -505,7 +505,7 @@ export const FileManagerModal = ({ if (filesWithIncorrectTypes.length > 0) { setErrorMessage( t( - `You've trying to upload files with incorrect type: {{incorrectTypeFileNames}}`, + `You're trying to upload files with incorrect type: {{incorrectTypeFileNames}}`, { incorrectTypeFileNames: filesWithIncorrectTypes.join(', '), }, diff --git a/apps/chat/src/components/Files/PreUploadModal.tsx b/apps/chat/src/components/Files/PreUploadModal.tsx index 0c0e702acf..49ccd2c2b4 100644 --- a/apps/chat/src/components/Files/PreUploadModal.tsx +++ b/apps/chat/src/components/Files/PreUploadModal.tsx @@ -139,7 +139,7 @@ export const PreUploadDialog = ({ if (incorrectTypeFiles.length > 0) { errors.push( t( - `You've trying to upload files with incorrect type: {{incorrectTypeFileNames}}`, + `You're trying to upload files with incorrect type: {{incorrectTypeFileNames}}`, { incorrectTypeFileNames: incorrectTypeFiles.join(', '), }, diff --git a/apps/chat/src/types/quick-apps.ts b/apps/chat/src/types/quick-apps.ts index 3c684a695d..4be1731142 100644 --- a/apps/chat/src/types/quick-apps.ts +++ b/apps/chat/src/types/quick-apps.ts @@ -3,4 +3,5 @@ export interface QuickAppConfig { model: string; temperature: number; web_api_toolset: object; + document_relative_url?: string; } diff --git a/apps/chat/src/utils/app/application.ts b/apps/chat/src/utils/app/application.ts index ca9b327bc0..a23c492c3a 100644 --- a/apps/chat/src/utils/app/application.ts +++ b/apps/chat/src/utils/app/application.ts @@ -55,6 +55,19 @@ export const regenerateApplicationId = ( return application as T; }; +export const mapApplicationPropertiesToApi = ( + properties: CustomApplicationModel['applicationProperties'], +) => { + if (properties?.document_relative_url) + return { + ...properties, + document_relative_url: ApiUtils.encodeApiUrl( + properties.document_relative_url as string, + ), + }; + return properties; +}; + export const convertApplicationToApi = ( applicationData: Omit, ): ApiApplicationModel => { @@ -69,7 +82,9 @@ export const convertApplicationToApi = ( reference: applicationData.reference || undefined, description_keywords: applicationData.topics, applicationTypeSchemaId: applicationData.applicationTypeSchemaId, - applicationProperties: applicationData.applicationProperties, + applicationProperties: mapApplicationPropertiesToApi( + applicationData.applicationProperties, + ), }; if (applicationData.function) { @@ -95,6 +110,19 @@ export const convertApplicationToApi = ( }; }; +export const mapApplicationPropertiesFromApi = ( + properties: CustomApplicationModel['applicationProperties'], +) => { + if (properties?.document_relative_url) + return { + ...properties, + document_relative_url: ApiUtils.decodeApiUrl( + properties.document_relative_url as string, + ), + }; + return properties; +}; + export const convertApplicationFromApi = ( application: ApiApplicationResponse, ): CustomApplicationModel => { @@ -123,7 +151,9 @@ export const convertApplicationFromApi = ( folderId: getFolderIdFromEntityId(id), topics: application.description_keywords, applicationTypeSchemaId: application.application_type_schema_id, - applicationProperties: application.application_properties, + applicationProperties: mapApplicationPropertiesFromApi( + application.application_properties, + ), ...(appFunction && { function: appFunction, functionStatus: appFunction.status, @@ -158,15 +188,21 @@ export const createQuickAppConfig = ({ instructions, temperature, config, + model, + document_relative_url, }: { instructions: string; temperature: number; config: string; + model: string; + document_relative_url: string; }): QuickAppConfig => ({ instructions, temperature, web_api_toolset: JSON.parse(config ?? '{}'), - model: DefaultsService.get('quickAppsModel', DEFAULT_QUICK_APPS_MODEL), + model: + model ?? DefaultsService.get('quickAppsModel', DEFAULT_QUICK_APPS_MODEL), + document_relative_url: document_relative_url || undefined, }); export const topicToOption = (topic: string) => ({ From bd23f0ebff0eef3588d4b0f7d72f37b3aed980a0 Mon Sep 17 00:00:00 2001 From: jane-taleika Date: Thu, 30 Jan 2025 01:05:44 +0100 Subject: [PATCH 7/9] fix(chat): add aria-labels to accomodate accessibility level A (#3048) authored-by: Yauheniya Taleika --- .../src/components/Chat/ChatInput/SendMessageButton.tsx | 8 ++++++++ apps/chat/src/components/Header/CreateNewConversation.tsx | 1 + apps/chat/src/components/Header/User/ProfileButton.tsx | 1 + libs/overlay/src/lib/ChatOverlay.ts | 1 + libs/overlay/src/lib/ChatOverlayManager.ts | 3 +++ 5 files changed, 14 insertions(+) diff --git a/apps/chat/src/components/Chat/ChatInput/SendMessageButton.tsx b/apps/chat/src/components/Chat/ChatInput/SendMessageButton.tsx index 46a2779772..5df000c180 100644 --- a/apps/chat/src/components/Chat/ChatInput/SendMessageButton.tsx +++ b/apps/chat/src/components/Chat/ChatInput/SendMessageButton.tsx @@ -4,8 +4,12 @@ import { IconSend, } from '@tabler/icons-react'; +import { useTranslation } from 'next-i18next'; + import classNames from 'classnames'; +import { Translation } from '@/src/types/translation'; + import { ConversationsSelectors } from '@/src/store/conversations/conversations.reducers'; import { useAppSelector } from '@/src/store/hooks'; import { ModelsSelectors } from '@/src/store/models/models.reducers'; @@ -28,6 +32,8 @@ interface Props { export const SendMessageButton = Inversify.register( 'SendMessageButton', ({ isLastMessageError, onSend, isDisabled, tooltip, isLoading }: Props) => { + const { t } = useTranslation(Translation.Chat); + const isModelsLoading = useAppSelector( ModelsSelectors.selectModelsIsLoading, ); @@ -52,6 +58,7 @@ export const SendMessageButton = Inversify.register( isLastMessageError && 'text-error', isOverlay ? 'right-3' : 'right-4', )} + aria-label={`${t('Send a message')}`} onClick={onSend} data-qa="regenerate" > @@ -73,6 +80,7 @@ export const SendMessageButton = Inversify.register( 'absolute top-[calc(50%_-_12px)] rounded hover:text-accent-primary disabled:cursor-not-allowed disabled:text-secondary', isOverlay ? 'right-3' : 'right-4', )} + aria-label={`${t('Send a message')}`} onClick={onSend} disabled={disabled} data-qa={dataQa} diff --git a/apps/chat/src/components/Header/CreateNewConversation.tsx b/apps/chat/src/components/Header/CreateNewConversation.tsx index d352f8b626..adc4ac49ac 100644 --- a/apps/chat/src/components/Header/CreateNewConversation.tsx +++ b/apps/chat/src/components/Header/CreateNewConversation.tsx @@ -37,6 +37,7 @@ export const CreateNewConversation = ({ iconSize }: Props) => {
@@ -263,7 +263,7 @@ export const PromptModal: FC = ({ isOpen, onClose, onUpdatePrompt }) => { name="description" className={inputClassName} style={{ resize: 'none' }} - placeholder={t('A description for your prompt.') || ''} + placeholder={t('A description for your prompt.')} value={description} onChange={descriptionOnChangeHandler} rows={3} @@ -283,11 +283,9 @@ export const PromptModal: FC = ({ isOpen, onClose, onUpdatePrompt }) => { name="content" className={inputClassName} style={{ resize: 'none' }} - placeholder={ - t( - 'Prompt content. Use {{}} to denote a variable.\nEx: {{name|defaultValue}} is a {{adjective}} {{noun|defaultValue}}', - ) || '' - } + placeholder={t( + 'Prompt content. Use {{}} to denote a variable.\nEx: {{name|defaultValue}} is a {{adjective}} {{noun|defaultValue}}', + )} value={content} onChange={contentOnChangeHandler} onBlur={contentOnBlurHandler} @@ -320,11 +318,9 @@ export const PromptModal: FC = ({ isOpen, onClose, onUpdatePrompt }) => { heading={t('Confirm renaming prompt')} confirmLabel={t('Rename')} cancelLabel={t('Cancel')} - description={ - t( - 'Renaming will stop sharing and other users will no longer see this prompt.', - ) || '' - } + description={t( + 'Renaming will stop sharing and other users will no longer see this prompt.', + )} onClose={(result) => { setIsConfirmDialog(false); if (result) { diff --git a/apps/chat/src/components/Promptbar/components/PromptbarSettings.tsx b/apps/chat/src/components/Promptbar/components/PromptbarSettings.tsx index e808346f62..6f1a37dd4d 100644 --- a/apps/chat/src/components/Promptbar/components/PromptbarSettings.tsx +++ b/apps/chat/src/components/Promptbar/components/PromptbarSettings.tsx @@ -7,7 +7,7 @@ import { } from '@tabler/icons-react'; import { useMemo, useState } from 'react'; -import { useTranslation } from 'next-i18next'; +import { useTranslation } from '@/src/hooks/useTranslation'; import { getPromptRootId } from '@/src/utils/app/id'; @@ -138,9 +138,9 @@ export function PromptbarSettings() { { diff --git a/apps/chat/src/components/Search/Search.tsx b/apps/chat/src/components/Search/Search.tsx index abdc45c7da..690ddba203 100644 --- a/apps/chat/src/components/Search/Search.tsx +++ b/apps/chat/src/components/Search/Search.tsx @@ -1,7 +1,7 @@ import { IconSearch } from '@tabler/icons-react'; import { useCallback } from 'react'; -import { useTranslation } from 'next-i18next'; +import { useTranslation } from '@/src/hooks/useTranslation'; import { FeatureType } from '@/src/types/common'; import { SearchFilters } from '@/src/types/search'; @@ -44,7 +44,7 @@ export default function Search({ className="w-full bg-transparent py-2 pl-9 pr-8 text-[14px] leading-3 outline-none placeholder:text-secondary" data-qa="search-input" type="text" - placeholder={t(placeholder) || ''} + placeholder={t(placeholder)} value={searchTerm} onChange={handleSearchChange} /> diff --git a/apps/chat/src/components/Search/SearchFiltersView.tsx b/apps/chat/src/components/Search/SearchFiltersView.tsx index 58805b4737..611c72732d 100644 --- a/apps/chat/src/components/Search/SearchFiltersView.tsx +++ b/apps/chat/src/components/Search/SearchFiltersView.tsx @@ -1,10 +1,10 @@ import { IconCircleFilled, IconFilter } from '@tabler/icons-react'; import { useMemo, useState } from 'react'; -import { useTranslation } from 'next-i18next'; - import classNames from 'classnames'; +import { useTranslation } from '@/src/hooks/useTranslation'; + import { getNewSearchFiltersValue, isSearchFilterSelected, diff --git a/apps/chat/src/components/Settings/CustomLogoSelect.tsx b/apps/chat/src/components/Settings/CustomLogoSelect.tsx index 486823e5fd..ba0d3e4d1e 100644 --- a/apps/chat/src/components/Settings/CustomLogoSelect.tsx +++ b/apps/chat/src/components/Settings/CustomLogoSelect.tsx @@ -1,10 +1,10 @@ import { IconX } from '@tabler/icons-react'; import { MouseEvent, useState } from 'react'; -import { useTranslation } from 'next-i18next'; - import classNames from 'classnames'; +import { useTranslation } from '@/src/hooks/useTranslation'; + import { Translation } from '@/src/types/translation'; import Tooltip from '../Common/Tooltip'; @@ -95,8 +95,8 @@ export const CustomLogoSelect = ({ setIsSelectFilesDialogOpened(false); }} headerLabel={fileManagerModalTitle || t('Select custom logo')} - customButtonLabel={t('Select file') as string} - customUploadButtonLabel={t('Upload files') as string} + customButtonLabel={t('Select file')} + customUploadButtonLabel={t('Upload files')} forceShowSelectCheckBox /> )} diff --git a/apps/chat/src/components/Settings/SettingDialog.tsx b/apps/chat/src/components/Settings/SettingDialog.tsx index 5596db9ae7..0194c68754 100644 --- a/apps/chat/src/components/Settings/SettingDialog.tsx +++ b/apps/chat/src/components/Settings/SettingDialog.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useTranslation } from 'next-i18next'; +import { useTranslation } from '@/src/hooks/useTranslation'; import { splitEntityId } from '@/src/utils/app/folders'; import { isSmallScreen } from '@/src/utils/app/mobile'; diff --git a/apps/chat/src/components/Settings/ThemeSelect.tsx b/apps/chat/src/components/Settings/ThemeSelect.tsx index c48b9c0d4a..13e384e4ac 100644 --- a/apps/chat/src/components/Settings/ThemeSelect.tsx +++ b/apps/chat/src/components/Settings/ThemeSelect.tsx @@ -1,9 +1,9 @@ import { MouseEvent, useMemo, useState } from 'react'; -import { useTranslation } from 'next-i18next'; - import classNames from 'classnames'; +import { useTranslation } from '@/src/hooks/useTranslation'; + import { Translation } from '@/src/types/translation'; import { useAppSelector } from '@/src/store/hooks'; diff --git a/apps/chat/src/components/Sidebar/Sidebar.tsx b/apps/chat/src/components/Sidebar/Sidebar.tsx index 3f9cb68375..19a72e8584 100644 --- a/apps/chat/src/components/Sidebar/Sidebar.tsx +++ b/apps/chat/src/components/Sidebar/Sidebar.tsx @@ -8,10 +8,10 @@ import { useState, } from 'react'; -import { useTranslation } from 'next-i18next'; - import classNames from 'classnames'; +import { useTranslation } from '@/src/hooks/useTranslation'; + import { EnumMapper } from '@/src/utils/app/mappers'; import { isMediumScreen, isSmallScreen } from '@/src/utils/app/mobile'; import { hasDragEventEntityData } from '@/src/utils/app/move'; diff --git a/apps/chat/src/components/VisualalizerRenderer/VisualizerRenderer.tsx b/apps/chat/src/components/VisualalizerRenderer/VisualizerRenderer.tsx index f4f7057a69..e6cf424d8c 100644 --- a/apps/chat/src/components/VisualalizerRenderer/VisualizerRenderer.tsx +++ b/apps/chat/src/components/VisualalizerRenderer/VisualizerRenderer.tsx @@ -1,7 +1,7 @@ import { IconRefresh } from '@tabler/icons-react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useTranslation } from 'next-i18next'; +import { useTranslation } from '@/src/hooks/useTranslation'; import { CustomVisualizer } from '@/src/types/custom-visualizers'; import { Translation } from '@/src/types/translation'; diff --git a/apps/chat/src/hooks/useHandleFileFolders.ts b/apps/chat/src/hooks/useHandleFileFolders.ts index 479df143ea..352b88465c 100644 --- a/apps/chat/src/hooks/useHandleFileFolders.ts +++ b/apps/chat/src/hooks/useHandleFileFolders.ts @@ -1,6 +1,6 @@ import { Dispatch, SetStateAction, useCallback } from 'react'; -import { useTranslation } from 'next-i18next'; +import { useTranslation } from '@/src/hooks/useTranslation'; import { getChildAndCurrentFoldersIdsById, @@ -50,7 +50,7 @@ export const useHandleFileFolders = ( const error = validateFolderRenaming(folders, newName, folderId); if (error) { - setErrorMessage(t(error) as string); + setErrorMessage(t(error)); return; } diff --git a/apps/chat/src/hooks/useTranslation.ts b/apps/chat/src/hooks/useTranslation.ts new file mode 100644 index 0000000000..2b034095fc --- /dev/null +++ b/apps/chat/src/hooks/useTranslation.ts @@ -0,0 +1,18 @@ +import { useCallback } from 'react'; + +import { useTranslation as useNextTranslation } from 'next-i18next'; + +import { Translation, TranslationOptions } from '../types/translation'; + +export const useTranslation = (translationNamespace: Translation) => { + const { t } = useNextTranslation(translationNamespace); + + const translate = useCallback( + (key: string, options?: TranslationOptions) => + ((options ? t(key, options) : t(key)) as unknown as string) ?? key ?? '', + [t], + ); + return { + t: translate, + }; +}; diff --git a/apps/chat/src/store/addons/addons.reducers.ts b/apps/chat/src/store/addons/addons.reducers.ts index 58d1bad161..b7f59f4c4c 100644 --- a/apps/chat/src/store/addons/addons.reducers.ts +++ b/apps/chat/src/store/addons/addons.reducers.ts @@ -4,6 +4,7 @@ import { translate } from '@/src/utils/app/translation'; import { ErrorMessage } from '@/src/types/error'; import { DialAIEntityAddon } from '@/src/types/models'; +import { Translation } from '@/src/types/translation'; import { errorsMessages } from '@/src/constants/errors'; @@ -68,7 +69,11 @@ export const addonsSlice = createSlice({ code: payload.error.status?.toString() ?? 'unknown', messageLines: payload.error.statusText ? [payload.error.statusText] - : [translate(errorsMessages.generalServer, { ns: 'common' })], + : [ + translate(errorsMessages.generalServer, { + ns: Translation.Common, + }), + ], } as ErrorMessage; }, initRecentAddons: ( diff --git a/apps/chat/src/store/files/files.epics.ts b/apps/chat/src/store/files/files.epics.ts index cb3762f822..48620f1c8b 100644 --- a/apps/chat/src/store/files/files.epics.ts +++ b/apps/chat/src/store/files/files.epics.ts @@ -22,6 +22,7 @@ import { ApiUtils } from '@/src/utils/server/api'; import { FeatureType } from '@/src/types/common'; import { AppEpic } from '@/src/types/store'; +import { Translation } from '@/src/types/translation'; import { PublicationActions } from '../publication/publication.reducers'; import { UIActions, UISelectors } from '../ui/ui.reducers'; @@ -230,7 +231,7 @@ const deleteFileFailEpic: AppEpic = (action$) => message: translate( 'Deleting file {{fileName}} failed. Please try again later', { - ns: 'file', + ns: Translation.Files, fileName: payload.fileName, }, ), diff --git a/apps/chat/src/store/import-export/importExport.epics.ts b/apps/chat/src/store/import-export/importExport.epics.ts index 6e2fff00af..64df4884e4 100644 --- a/apps/chat/src/store/import-export/importExport.epics.ts +++ b/apps/chat/src/store/import-export/importExport.epics.ts @@ -80,6 +80,7 @@ import { HTTPMethod } from '@/src/types/http'; import { LatestExportFormat, ReplaceOptions } from '@/src/types/import-export'; import { Prompt } from '@/src/types/prompt'; import { AppEpic } from '@/src/types/store'; +import { Translation } from '@/src/types/translation'; import { PromptsActions, @@ -407,7 +408,7 @@ const importPromptsEpic: AppEpic = (action$) => of( UIActions.showErrorToast( translate(errorsMessages.unsupportedPromptsDataFormat, { - ns: 'common', + ns: Translation.Common, }), ), ), diff --git a/apps/chat/src/store/models/models.reducers.ts b/apps/chat/src/store/models/models.reducers.ts index 6726c9c631..ade5de02ca 100644 --- a/apps/chat/src/store/models/models.reducers.ts +++ b/apps/chat/src/store/models/models.reducers.ts @@ -10,6 +10,7 @@ import { InstalledModel, PublishRequestDialAIEntityModel, } from '@/src/types/models'; +import { Translation } from '@/src/types/translation'; import { RECENT_MODELS_COUNT } from '@/src/constants/chat'; import { errorsMessages } from '@/src/constants/errors'; @@ -112,7 +113,11 @@ export const modelsSlice = createSlice({ code: payload.error.status?.toString() ?? 'unknown', messageLines: payload.error.statusText ? [payload.error.statusText] - : [translate(errorsMessages.generalServer, { ns: 'common' })], + : [ + translate(errorsMessages.generalServer, { + ns: Translation.Common, + }), + ], } as ErrorMessage; }, diff --git a/apps/chat/src/store/service/service.epics.ts b/apps/chat/src/store/service/service.epics.ts index 9d664d1065..26dc0bee57 100644 --- a/apps/chat/src/store/service/service.epics.ts +++ b/apps/chat/src/store/service/service.epics.ts @@ -7,6 +7,7 @@ import { ApiUtils } from '@/src/utils/server/api'; import { HTTPMethod } from '@/src/types/http'; import { AppEpic } from '@/src/types/store'; +import { Translation } from '@/src/types/translation'; import { errorsMessages } from '@/src/constants/errors'; @@ -48,7 +49,7 @@ const reportIssueFailEpic: AppEpic = (action$) => of( UIActions.showErrorToast( translate(errorsMessages.generalServer, { - ns: 'common', + ns: Translation.Common, }), ), ), @@ -92,7 +93,7 @@ const requestApiKeyFailEpic: AppEpic = (action$) => of( UIActions.showErrorToast( translate(errorsMessages.generalServer, { - ns: 'common', + ns: Translation.Common, }), ), ), diff --git a/apps/chat/src/types/translation.ts b/apps/chat/src/types/translation.ts index 2f76e82c3a..f36d630843 100644 --- a/apps/chat/src/types/translation.ts +++ b/apps/chat/src/types/translation.ts @@ -9,3 +9,7 @@ export enum Translation { Marketplace = 'marketplace', Header = 'header', } + +export type TranslationOptions = Record & { + ns?: Translation; +}; diff --git a/apps/chat/src/utils/app/file.ts b/apps/chat/src/utils/app/file.ts index 0b6fb061f1..093bc207c4 100644 --- a/apps/chat/src/utils/app/file.ts +++ b/apps/chat/src/utils/app/file.ts @@ -1,5 +1,3 @@ -import { TFunction } from 'next-i18next'; - import { BucketService } from '@/src/utils/app/data/bucket-service'; import { Conversation } from '@/src/types/chat'; @@ -287,7 +285,7 @@ export const getExtensionsListForMimeTypes = (mimeTypes: string[]) => { export const getShortExtensionsListFromMimeType = ( mimeTypes: string[], - t: TFunction, + t: (key: string) => string, ) => { return uniq( mimeTypes diff --git a/apps/chat/src/utils/app/translation.ts b/apps/chat/src/utils/app/translation.ts index 11d9f8f32d..7197f12029 100644 --- a/apps/chat/src/utils/app/translation.ts +++ b/apps/chat/src/utils/app/translation.ts @@ -1,5 +1,10 @@ import { i18n } from 'next-i18next'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const translate = (text: string, options?: any) => - i18n ? (i18n.t(text, options) as unknown as string) : text; +import { TranslationOptions } from '@/src/types/translation'; + +export const translate = (text: string, options?: TranslationOptions) => + i18n + ? options + ? i18n.t(text, options) + : (i18n.t(text) as unknown as string) + : text;