diff --git a/apps/chat/src/store/publication/publication.epics.ts b/apps/chat/src/store/publication/publication.epics.ts index bd9450ceb..2b357e09b 100644 --- a/apps/chat/src/store/publication/publication.epics.ts +++ b/apps/chat/src/store/publication/publication.epics.ts @@ -34,8 +34,6 @@ import { splitEntityId, } from '@/src/utils/app/folders'; import { - getConversationRootId, - getPromptRootId, isApplicationId, isConversationId, isFileId, @@ -43,13 +41,14 @@ import { isRootId, } from '@/src/utils/app/id'; import { + getItemsIdsToRemoveAndHide, isEntityIdPublic, mapPublishedItems, } from '@/src/utils/app/publications'; import { translate } from '@/src/utils/app/translation'; import { ApiUtils, - getPublicItemIdWithoutVersion, + getIdWithoutVersionFromApiKey, parseConversationApiKey, parsePromptApiKey, } from '@/src/utils/server/api'; @@ -62,7 +61,6 @@ import { AppEpic } from '@/src/types/store'; import { DEFAULT_CONVERSATION_NAME } from '@/src/constants/default-ui-settings'; import { errorsMessages } from '@/src/constants/errors'; -import { NA_VERSION, PUBLIC_URL_PREFIX } from '@/src/constants/public'; import { AuthSelectors } from '../auth/auth.reducers'; import { @@ -782,7 +780,6 @@ const approvePublicationEpic: AppEpic = (action$, state$) => switchMap(({ payload }) => PublicationService.approvePublication(payload.url).pipe( switchMap(() => { - const actions: Observable[] = []; const selectedPublication = PublicationSelectors.selectSelectedPublication(state$.value); @@ -790,50 +787,60 @@ const approvePublicationEpic: AppEpic = (action$, state$) => return of(PublicationActions.approvePublicationFail()); } + const actions: Observable[] = []; + const state = state$.value; + + const resourcesToReview = + PublicationSelectors.selectResourcesToReview(state); + const conversationResources = selectedPublication.resources.filter( - (r) => isConversationId(r.targetUrl), + (resource) => isConversationId(resource.targetUrl), ); const conversationResourcesToPublish = conversationResources.filter( - (r) => r.action === PublishActions.ADD, + (resource) => resource.action === PublishActions.ADD, ); const conversationResourcesToUnpublish = conversationResources.filter( - (r) => r.action === PublishActions.DELETE, + (resource) => resource.action === PublishActions.DELETE, ); if (conversationResourcesToUnpublish.length) { - const allConversations = ConversationsSelectors.selectConversations( - state$.value, - ); - const allFolders = ConversationsSelectors.selectFolders( - state$.value, - ); - const conversationsToRemove = conversationResourcesToUnpublish.map( - (r) => r.reviewUrl, - ); + const allConversations = + ConversationsSelectors.selectConversations(state); + const allFolders = ConversationsSelectors.selectFolders(state); + + const { itemsToHideIds, itemsToRemoveIds } = + getItemsIdsToRemoveAndHide( + conversationResourcesToUnpublish, + resourcesToReview, + ); + const idsToExclude = [...itemsToRemoveIds, ...itemsToHideIds]; const filteredConversations = allConversations.filter( - (c) => !conversationsToRemove.includes(c.id), + (conv) => !idsToExclude.includes(conv.id), ); - const filteredFolders = allFolders.filter( - (f) => - f.status !== UploadStatus.LOADED || - !f.id.startsWith(getConversationRootId(PUBLIC_URL_PREFIX)) || - (filteredConversations.some((c) => - c.id.startsWith(`${f.id}/`), - ) && - f.id.startsWith(getConversationRootId(PUBLIC_URL_PREFIX))), - ); - const versionGroups = uniq( - conversationsToRemove.map((id) => { - const parsedApiKey = parseConversationApiKey( - splitEntityId(id).name, - { parseVersion: true }, - ); + const filteredFolders = allFolders.filter((folder) => { + const isNotLoaded = folder.status !== UploadStatus.LOADED; + const isPublic = !isEntityIdPublic(folder); + const hasConversations = filteredConversations.some((conv) => + conv.id.startsWith(`${folder.id}/`), + ); - return getPublicItemIdWithoutVersion( - parsedApiKey.publicationInfo?.version ?? NA_VERSION, - id, - ); - }), + return isNotLoaded || isPublic || hasConversations; + }); + const foldersToHide = allFolders.filter((folder) => { + const hasConversations = filteredConversations.some((conv) => + conv.id.startsWith(`${folder.id}/`), + ); + const hasHiddenConversations = itemsToHideIds.some((convId) => + convId.startsWith(`${folder.id}/`), + ); + + return !hasConversations && hasHiddenConversations; + }); + + const versionGroups = uniq( + itemsToRemoveIds.map((id) => + getIdWithoutVersionFromApiKey(id, parseConversationApiKey), + ), ); actions.push( @@ -842,27 +849,51 @@ const approvePublicationEpic: AppEpic = (action$, state$) => conversations: filteredConversations, }), ), + of( + ConversationsActions.addConversations({ + conversations: allConversations + .filter((conv) => itemsToHideIds.includes(conv.id)) + .map((conv) => ({ + status: undefined, + id: conv.id, + model: { id: conv.model.id }, + name: conv.name, + folderId: conv.folderId, + publicationInfo: { + isNotExist: true, + }, + publishedWithMe: false, + })), + }), + ), of( ConversationsActions.setFolders({ folders: filteredFolders, }), ), + of( + ConversationsActions.addFolders({ + folders: foldersToHide.map((folder) => ({ + ...folder, + publishedWithMe: false, + })), + }), + ), + of( + PublicationActions.markResourcesAsReviewedByIds({ + ids: itemsToHideIds, + }), + ), of( PublicationActions.removePublicVersionGroups({ groupsToRemove: versionGroups.map((groupId) => ({ - groupIds: conversationsToRemove.filter((id) => { - const parsedApiKey = parseConversationApiKey( - splitEntityId(id).name, - { parseVersion: true }, - ); - - return ( - getPublicItemIdWithoutVersion( - parsedApiKey.publicationInfo?.version ?? NA_VERSION, + groupIds: idsToExclude.filter( + (id) => + getIdWithoutVersionFromApiKey( id, - ) === groupId - ); - }), + parseConversationApiKey, + ) === groupId, + ), versionGroupId: groupId, })), }), @@ -875,7 +906,7 @@ const approvePublicationEpic: AppEpic = (action$, state$) => conversationResourcesToPublish.flatMap((resource) => getParentFolderIdsFromEntityId( getFolderIdFromEntityId(resource.targetUrl), - ).filter((p) => p !== resource.targetUrl), + ).filter((folderId) => folderId !== resource.targetUrl), ), ); @@ -900,7 +931,13 @@ const approvePublicationEpic: AppEpic = (action$, state$) => ), of( ConversationsActions.addConversations({ - conversations: items, + conversations: items.map((item) => { + if (item.publicationInfo?.isNotExist) { + item.publicationInfo.isNotExist = false; + } + + return item; + }), }), ), of( @@ -911,42 +948,50 @@ const approvePublicationEpic: AppEpic = (action$, state$) => ); } - const promptResources = selectedPublication.resources.filter((r) => - isPromptId(r.targetUrl), + const promptResources = selectedPublication.resources.filter( + (resource) => isPromptId(resource.targetUrl), ); const promptResourcesToPublish = promptResources.filter( - (r) => r.action === PublishActions.ADD, + (resource) => resource.action === PublishActions.ADD, ); const promptResourcesToUnpublish = promptResources.filter( - (r) => r.action === PublishActions.DELETE, + (resource) => resource.action === PublishActions.DELETE, ); if (promptResourcesToUnpublish.length) { - const allPrompts = PromptsSelectors.selectPrompts(state$.value); - const promptsToRemove = promptResourcesToUnpublish.map( - (r) => r.reviewUrl, - ); - const allFolders = PromptsSelectors.selectFolders(state$.value); + const allPrompts = PromptsSelectors.selectPrompts(state); + const allFolders = PromptsSelectors.selectFolders(state); + const { itemsToHideIds, itemsToRemoveIds } = + getItemsIdsToRemoveAndHide( + promptResourcesToUnpublish, + resourcesToReview, + ); + const idsToExclude = [...itemsToRemoveIds, ...itemsToHideIds]; const filteredPrompts = allPrompts.filter( - (p) => !promptsToRemove.includes(p.id), - ); - const filteredFolders = allFolders.filter( - (f) => - f.status !== UploadStatus.LOADED || - !f.id.startsWith(getPromptRootId(PUBLIC_URL_PREFIX)) || - (filteredPrompts.some((c) => c.id.startsWith(`${f.id}/`)) && - f.id.startsWith(getPromptRootId(PUBLIC_URL_PREFIX))), + (prompt) => !idsToExclude.includes(prompt.id), ); + const filteredFolders = allFolders.filter((folder) => { + const isNotLoaded = folder.status !== UploadStatus.LOADED; + const isPublic = !isEntityIdPublic(folder); + const hasPrompts = filteredPrompts.some((prompt) => + prompt.id.startsWith(`${folder.id}/`), + ); + + return isNotLoaded || isPublic || hasPrompts; + }); + const foldersToHide = allFolders.filter((folder) => { + const hasPrompts = filteredPrompts.some((prompt) => + prompt.id.startsWith(`${folder.id}/`), + ); + const hasHiddenPrompts = itemsToHideIds.some((promptId) => + promptId.startsWith(`${folder.id}/`), + ); + + return !hasPrompts && hasHiddenPrompts; + }); const versionGroups = uniq( - promptsToRemove.map((id) => { - const parsedApiKey = parsePromptApiKey(splitEntityId(id).name, { - parseVersion: true, - }); - - return getPublicItemIdWithoutVersion( - parsedApiKey.publicationInfo?.version ?? NA_VERSION, - id, - ); + itemsToRemoveIds.map((id) => { + return getIdWithoutVersionFromApiKey(id, parsePromptApiKey); }), ); @@ -956,25 +1001,47 @@ const approvePublicationEpic: AppEpic = (action$, state$) => prompts: filteredPrompts, }), ), + of( + PromptsActions.addPrompts({ + prompts: allPrompts + .filter((prompt) => itemsToHideIds.includes(prompt.id)) + .map((prompt) => ({ + status: undefined, + id: prompt.id, + name: prompt.name, + folderId: prompt.folderId, + publicationInfo: { + isNotExist: true, + }, + publishedWithMe: false, + })), + }), + ), of( PromptsActions.setFolders({ folders: filteredFolders, }), ), + of( + PromptsActions.addFolders({ + folders: foldersToHide.map((folder) => ({ + ...folder, + publishedWithMe: false, + })), + }), + ), + of( + PublicationActions.markResourcesAsReviewedByIds({ + ids: itemsToHideIds, + }), + ), of( PublicationActions.removePublicVersionGroups({ groupsToRemove: versionGroups.map((groupId) => ({ - groupIds: promptsToRemove.filter((id) => { - const parsedApiKey = parseConversationApiKey( - splitEntityId(id).name, - { parseVersion: true }, - ); - + groupIds: idsToExclude.filter((id) => { return ( - getPublicItemIdWithoutVersion( - parsedApiKey.publicationInfo?.version ?? NA_VERSION, - id, - ) === groupId + getIdWithoutVersionFromApiKey(id, parsePromptApiKey) === + groupId ); }), versionGroupId: groupId, @@ -989,7 +1056,7 @@ const approvePublicationEpic: AppEpic = (action$, state$) => promptResourcesToPublish.flatMap((resource) => getParentFolderIdsFromEntityId( getFolderIdFromEntityId(resource.targetUrl), - ).filter((p) => p !== resource.targetUrl), + ).filter((folderId) => folderId !== resource.targetUrl), ), ); @@ -1013,7 +1080,13 @@ const approvePublicationEpic: AppEpic = (action$, state$) => ), of( PromptsActions.addPrompts({ - prompts: items, + prompts: items.map((item) => { + if (item.publicationInfo?.isNotExist) { + item.publicationInfo.isNotExist = false; + } + + return item; + }), }), ), of( diff --git a/apps/chat/src/store/publication/publication.reducers.ts b/apps/chat/src/store/publication/publication.reducers.ts index 3a8970059..04c090223 100644 --- a/apps/chat/src/store/publication/publication.reducers.ts +++ b/apps/chat/src/store/publication/publication.reducers.ts @@ -163,6 +163,20 @@ export const publicationSlice = createSlice({ : resource, ); }, + markResourcesAsReviewedByIds: ( + state, + { + payload, + }: PayloadAction<{ + ids: string[]; + }>, + ) => { + state.resourcesToReview = state.resourcesToReview.map((resource) => + payload.ids.includes(resource.reviewUrl) + ? { ...resource, reviewed: true } + : resource, + ); + }, uploadRules: (state, _action: PayloadAction<{ path: string }>) => { state.isRulesLoading = true; }, diff --git a/apps/chat/src/utils/app/publications.ts b/apps/chat/src/utils/app/publications.ts index bcbaec46c..f2834d42c 100644 --- a/apps/chat/src/utils/app/publications.ts +++ b/apps/chat/src/utils/app/publications.ts @@ -1,6 +1,10 @@ import { FeatureType } from '@/src/types/common'; import { PromptInfo } from '@/src/types/prompt'; -import { PublicVersionGroups } from '@/src/types/publication'; +import { + PublicVersionGroups, + PublicationResource, + ResourceToReview, +} from '@/src/types/publication'; import { SharingType } from '@/src/types/share'; import { @@ -170,3 +174,34 @@ export const mapPublishedItems = ( export const getPublicationId = (url: string) => url.split('/').slice(-1).shift(); + +export const getItemsIdsToRemoveAndHide = ( + allResources: PublicationResource[], + resourcesToReview: ResourceToReview[], +) => { + const itemsToHide: PublicationResource[] = []; + const itemsToRemove: PublicationResource[] = []; + + const reviewUrlCountMap = resourcesToReview.reduce>( + (acc, res) => { + acc[res.reviewUrl] = (acc[res.reviewUrl] || 0) + 1; + return acc; + }, + {}, + ); + + allResources.forEach((resource) => { + const count = reviewUrlCountMap[resource.reviewUrl] || 0; + + if (count > 1) { + itemsToHide.push(resource); + } else { + itemsToRemove.push(resource); + } + }); + + return { + itemsToHideIds: itemsToHide.map((item) => item.reviewUrl), + itemsToRemoveIds: itemsToRemove.map((item) => item.reviewUrl), + }; +}; diff --git a/apps/chat/src/utils/server/api.ts b/apps/chat/src/utils/server/api.ts index 02e68f52d..acbf8df84 100644 --- a/apps/chat/src/utils/server/api.ts +++ b/apps/chat/src/utils/server/api.ts @@ -14,6 +14,7 @@ import { NA_VERSION } from '@/src/constants/public'; import { validVersionRegEx } from '@/src/constants/versions'; import { constructPath } from '../app/file'; +import { splitEntityId } from '../app/folders'; import { ConversationInfo } from '@epam/ai-dial-shared'; @@ -291,3 +292,20 @@ export const isValidEntityApiType = (apiKey: string): boolean => { Object.values(CoreApiKeys).includes(apiKey as CoreApiKeys) ); }; + +export const getIdWithoutVersionFromApiKey = ( + id: string, + parseMethod: + | typeof parseApplicationApiKey + | typeof parseConversationApiKey + | typeof parsePromptApiKey, +) => { + const parsedApiKey = parseMethod(splitEntityId(id).name, { + parseVersion: true, + }); + + return getPublicItemIdWithoutVersion( + parsedApiKey.publicationInfo?.version ?? NA_VERSION, + id, + ); +};