apps/chat/src/utils/app/common.ts (207 lines of code) (raw):
import { notAllowedSymbolsRegex } from '@/src/utils/app/file';
import { getFoldersFromIds, splitEntityId } from '@/src/utils/app/folders';
import { PrepareNameOptions } from '@/src/types/chat';
import { FolderInterface, FolderType } from '@/src/types/folder';
import {
PublicVersionGroups,
PublicVersionOption,
} from '@/src/types/publication';
import { EntityFilters } from '@/src/types/search';
import { MAX_ENTITY_LENGTH } from '@/src/constants/default-ui-settings';
import { NA_VERSION } from '@/src/constants/public';
import { getPublicItemIdWithoutVersion } from '../server/api';
import { doesEntityContainSearchTerm } from './search';
import { Entity, ShareEntity } from '@epam/ai-dial-shared';
import groupBy from 'lodash-es/groupBy';
import keyBy from 'lodash-es/keyBy';
import merge from 'lodash-es/merge';
import orderBy from 'lodash-es/orderBy';
import trimEnd from 'lodash-es/trimEnd';
import uniq from 'lodash-es/uniq';
import values from 'lodash-es/values';
import { substring } from 'stringz';
/**
* Combine entities. If there are the same ids then will be used entity from entities1 i.e. first in array
* @param entities1
* @param entities2
* @returns new array without duplicates
*/
export const combineEntities = <T extends Entity>(
entities1: T[],
entities2: T[],
): T[] => {
const mergedEntities = merge(keyBy(entities2, 'id'), keyBy(entities1, 'id'));
return values(mergedEntities);
};
export const isEntityNameOnSameLevelUnique = (
nameToBeUnique: string,
entity: Entity,
entities: Entity[],
): boolean => {
const sameLevelEntities = entities.filter(
(e) => entity.id !== e.id && e.folderId === entity.folderId,
);
return !sameLevelEntities.some((e) => nameToBeUnique === e.name);
};
export const isImportEntityNameOnSameLevelUnique = ({
entity,
entities,
}: {
entity: Entity;
entities: Entity[];
}): boolean => {
return !entities.some((e) => {
const { apiKey, parentPath } = splitEntityId(e.id);
const { apiKey: importApiKey, parentPath: importParentPath } =
splitEntityId(entity.id);
return (
apiKey === importApiKey &&
parentPath === importParentPath &&
entity.name === e.name
);
});
};
export const doesHaveDotsInTheEnd = (name: string) => name.trim().endsWith('.');
export const isEntityNameInvalid = (name: string) =>
doesHaveDotsInTheEnd(name) || notAllowedSymbolsRegex.test(name);
export const hasInvalidNameInPath = (path: string) =>
path.split('/').some((part) => isEntityNameInvalid(part));
export const isEntityNameOrPathInvalid = (entity: Entity) =>
isEntityNameInvalid(entity.name) || hasInvalidNameInPath(entity.folderId);
export const filterOnlyMyEntities = <T extends ShareEntity>(
entities: T[],
): T[] =>
entities.filter((entity) => !entity.sharedWithMe && !entity.publishedWithMe);
export const filterMigratedEntities = <T extends Entity>(
entities: T[],
migratedEntityIds: string[],
notMigrated = false,
): T[] =>
entities.filter((entity) =>
notMigrated
? !migratedEntityIds.includes(entity.id)
: migratedEntityIds.includes(entity.id),
);
export const updateEntitiesFoldersAndIds = (
entities: Entity[],
folders: FolderInterface[],
updateFolderId: (folderId: string) => string,
openedFoldersIds: string[],
) => {
const allFolderIds = entities.map((prompt) => prompt.folderId as string);
const updatedExistedFolders = folders.map((f: FolderInterface) => ({
...f,
id: updateFolderId(f.id)!,
folderId: updateFolderId(f.folderId),
}));
const newUniqueFolderIds = uniq(allFolderIds).map((id) => updateFolderId(id));
const updatedFolders = combineEntities(
getFoldersFromIds(newUniqueFolderIds, FolderType.Chat),
updatedExistedFolders,
);
const updatedOpenedFoldersIds = openedFoldersIds.map(
(id) => updateFolderId(id)!,
);
return { updatedFolders, updatedOpenedFoldersIds };
};
export const trimEndDots = (str: string) => trimEnd(str, '. \t\r\n');
export const prepareEntityName = (
name: string,
options?: Partial<PrepareNameOptions>,
) => {
const clearName = options?.forRenaming
? name
.replace(
notAllowedSymbolsRegex,
options?.replaceWithSpacesForRenaming ? ' ' : '',
)
.trim()
: (name
.replace(/\r\n|\r/gm, '\n')
.split('\n')
.map((s) => s.replace(notAllowedSymbolsRegex, ' ').trim())
.filter(Boolean)[0] ?? '');
const result =
clearName.length > MAX_ENTITY_LENGTH
? substring(clearName, 0, MAX_ENTITY_LENGTH)
: clearName;
const additionalCuttedResult =
result.length > MAX_ENTITY_LENGTH
? result.substring(0, MAX_ENTITY_LENGTH)
: result;
return !options?.forRenaming || options?.trimEndDotsRequired
? trimEndDots(additionalCuttedResult)
: additionalCuttedResult.trim();
};
export const isSearchTermMatched = (entity: ShareEntity, searchTerm?: string) =>
!searchTerm || doesEntityContainSearchTerm(entity, searchTerm);
export const isSearchFilterMatched = (
entity: ShareEntity,
filters: EntityFilters,
) => filters.searchFilter?.(entity) ?? true;
export const isSectionFilterMatched = (
entity: ShareEntity,
filters: EntityFilters,
ignoreSectionFilter?: boolean,
) => ignoreSectionFilter || (filters.sectionFilter?.(entity) ?? true);
export const isVersionFilterMatched = (
entity: ShareEntity,
filters: EntityFilters,
versionGroups: PublicVersionGroups,
ignoreVersionFilter?: boolean,
) => {
if (ignoreVersionFilter) return true;
const version = entity.publicationInfo?.version;
if (!version || !filters.versionFilter) return true;
const currentVersionGroup =
versionGroups[getPublicItemIdWithoutVersion(version, entity.id)];
return currentVersionGroup
? filters.versionFilter(entity, currentVersionGroup.selectedVersion.version)
: true;
};
export const isVersionValid = (version: string | undefined) => {
if (!version) {
return false;
}
const versionParts = version.split('.');
return (
versionParts.length === 3 &&
versionParts.every((part) => /^\d+$/.test(part))
);
};
export const findLatestVersion = (versions: string[]) => {
const filteredVersions = versions.filter((v) => v !== NA_VERSION);
if (!filteredVersions.length) {
return NA_VERSION;
}
const sortedVersions = orderBy(
filteredVersions,
[(version) => version.split('.').map(Number)],
['asc'],
);
return sortedVersions.pop();
};
export const sortAllVersions = (
versions: NonNullable<PublicVersionGroups[string]>['allVersions'],
) =>
orderBy(
versions,
({ version }) => {
if (version === 'N/A') {
return [-1, -1, -1];
}
return version.split('.').map(Number);
},
['desc', 'desc', 'desc'],
);
export const groupAllVersions = (versions: PublicVersionOption[]) =>
Object.values(
groupBy(
versions.map((group) => group),
(group) => group.version.match(/^\d+\.\d+/),
),
).flatMap((group) => {
const latestVersion = findLatestVersion(
group.map(({ version }) => version),
);
const latestVersionItemId = group.find(
(item) => item.version === latestVersion,
)?.id;
return latestVersion && latestVersionItemId
? [{ version: latestVersion, id: latestVersionItemId }]
: [];
});