@@ -109,7 +103,6 @@ const getPlayerCaption = (entity: DialAIEntityModel) => {
interface ApplicationCardProps {
entity: DialAIEntityModel;
- isNotDesktop?: boolean;
onClick: (entity: DialAIEntityModel) => void;
onPublish?: (entity: DialAIEntityModel, action: PublishActions) => void;
onDelete?: (entity: DialAIEntityModel) => void;
@@ -119,7 +112,6 @@ interface ApplicationCardProps {
export const ApplicationCard = ({
entity,
- isNotDesktop,
onClick,
onDelete,
onEdit,
@@ -150,22 +142,9 @@ export const ApplicationCard = ({
const isExecutable =
isExecutableApp(entity) && (isMyApp || isAdmin || canWrite);
- const shareIconSize =
- screenState === ScreenState.DESKTOP
- ? DESKTOP_SHARE_ICON_SIZE
- : TABLET_SHARE_ICON_SIZE;
+ const { iconSize, shareIconSize } = CardIconSizes[screenState];
- const PlayerIcon = useMemo(() => {
- switch (playerStatus) {
- case SimpleApplicationStatus.DEPLOY:
- return IconPlayerPlay;
- case SimpleApplicationStatus.UNDEPLOY:
- return IconPlaystationSquare;
- case SimpleApplicationStatus.UPDATING:
- default:
- return LoaderIcon;
- }
- }, [playerStatus]);
+ const PlayerContextIcon = PlayerContextIcons[playerStatus];
const handleUpdateFunctionStatus = useCallback(() => {
dispatch(
@@ -207,14 +186,8 @@ export const ApplicationCard = ({
disabled: playerStatus === SimpleApplicationStatus.UPDATING,
display:
(isAdmin || isMyApp) && !!entity.functionStatus && isCodeAppsEnabled,
- Icon: PlayerIcon,
- iconClassName: classNames({
- ['text-error']: playerStatus === SimpleApplicationStatus.UNDEPLOY,
- ['text-accent-secondary']:
- playerStatus === SimpleApplicationStatus.DEPLOY,
- ['animate-spin-steps']:
- playerStatus === SimpleApplicationStatus.UPDATING,
- }),
+ Icon: PlayerContextIcon,
+ iconClassName: PlayerContextIconClasses[playerStatus],
onClick: (e: React.MouseEvent) => {
e.stopPropagation();
handleUpdateFunctionStatus();
@@ -302,7 +275,7 @@ export const ApplicationCard = ({
isAdmin,
isMyApp,
isCodeAppsEnabled,
- PlayerIcon,
+ PlayerContextIcon,
canWrite,
onEdit,
isApplicationsSharingEnabled,
@@ -316,8 +289,6 @@ export const ApplicationCard = ({
],
);
- const iconSize =
- (isNotDesktop ?? isMediumScreen()) ? SMALL_ICON_SIZE : DESKTOP_ICON_SIZE;
const Bookmark = installedModelIds.has(entity.reference)
? IconBookmarkFilled
: IconBookmark;
@@ -326,7 +297,7 @@ export const ApplicationCard = ({
<>
onClick(entity)}
- className="group relative h-[162px] cursor-pointer rounded-md bg-layer-2 p-4 shadow-card hover:bg-layer-3 xl:h-[164px] xl:p-5"
+ className="group relative h-[98px] cursor-pointer rounded-md bg-layer-2 p-3 shadow-card hover:bg-layer-3 md:h-[162px] md:p-4 xl:h-[164px] xl:p-5"
data-qa="agent"
>
diff --git a/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationCopyLink.tsx b/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationCopyLink.tsx
index d7d42a0b0..8112a6a8d 100644
--- a/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationCopyLink.tsx
+++ b/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationCopyLink.tsx
@@ -1,30 +1,42 @@
import { IconCheck, IconLink } from '@tabler/icons-react';
import { MouseEvent, useCallback, useMemo, useRef, useState } from 'react';
+import classNames from 'classnames';
+
import { useTranslation } from '@/src/hooks/useTranslation';
import { PageType } from '@/src/types/common';
-import { DialAIEntityModel } from '@/src/types/models';
import { Translation } from '@/src/types/translation';
import { MarketplaceQueryParams } from '@/src/constants/marketplace';
+import Tooltip from '../../Common/Tooltip';
+
interface ApplicationCopyLinkProps {
- entity: DialAIEntityModel;
+ reference: string;
+ size?: number;
+ withText?: boolean;
+ hasTooltip?: boolean;
+ className?: string;
}
-const ICON_SIZE = 20;
const TRIGGER_CLASS =
'flex items-center gap-2 whitespace-nowrap px-3 py-1.5 text-sm text-accent-primary outline-none';
-export function ApplicationCopyLink({ entity }: ApplicationCopyLinkProps) {
+export function ApplicationCopyLink({
+ reference,
+ size = 20,
+ withText,
+ hasTooltip,
+ className,
+}: ApplicationCopyLinkProps) {
const { t } = useTranslation(Translation.Marketplace);
const [urlCopied, setUrlCopied] = useState(false);
const timeoutRef = useRef
>();
const link = useMemo(
() =>
- `${window.location.origin}/${PageType.Marketplace}?${MarketplaceQueryParams.model}=${entity.reference}`,
- [entity.reference],
+ `${window.location.origin}/${PageType.Marketplace}?${MarketplaceQueryParams.model}=${reference}`,
+ [reference],
);
const handleCopy = useCallback(
(e: MouseEvent) => {
@@ -44,23 +56,25 @@ export function ApplicationCopyLink({ entity }: ApplicationCopyLinkProps) {
);
return (
- <>
+
{urlCopied ? (
-
-
-
{t('Copied!')}
+
+
+ {withText && {t('Copied!')}}
) : (
-
- {t('Copy link')}
+
+ {withText && {t('Copy link')}}
)}
- >
+
);
}
diff --git a/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationFooter.tsx b/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationFooter.tsx
index f4a23c260..b43b53409 100644
--- a/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationFooter.tsx
+++ b/apps/chat/src/components/Marketplace/ApplicationDetails/ApplicationFooter.tsx
@@ -3,25 +3,28 @@ import {
IconBookmarkFilled,
IconEdit,
IconFileDescription,
+ IconLink,
IconPlayerPlay,
- IconPlaystationSquare,
IconTrashX,
+ IconUserShare,
IconWorldShare,
} from '@tabler/icons-react';
import { useCallback, useMemo, useState } from 'react';
import classNames from 'classnames';
+import { useScreenState } from '@/src/hooks/useScreenState';
import { useTranslation } from '@/src/hooks/useTranslation';
import {
getApplicationNextStatus,
getApplicationSimpleStatus,
isApplicationDeploymentInProgress,
+ isApplicationPublic,
isApplicationStatusUpdating,
isExecutableApp,
} from '@/src/utils/app/application';
-import { isApplicationId, isMyApplication } from '@/src/utils/app/id';
+import { isMyApplication } from '@/src/utils/app/id';
import { isEntityIdPublic } from '@/src/utils/app/publications';
import { canWriteSharedWithMe } from '@/src/utils/app/share';
@@ -29,6 +32,8 @@ import {
ApplicationStatus,
SimpleApplicationStatus,
} from '@/src/types/applications';
+import { FeatureType, PageType, ScreenState } from '@/src/types/common';
+import { DisplayMenuItemProps } from '@/src/types/menu';
import { DialAIEntityModel } from '@/src/types/models';
import { Translation } from '@/src/types/translation';
@@ -38,12 +43,21 @@ import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
import { ModelsSelectors } from '@/src/store/models/models.reducers';
import { SettingsSelectors } from '@/src/store/settings/settings.reducers';
import { ShareActions } from '@/src/store/share/share.reducers';
+import { UIActions } from '@/src/store/ui/ui.reducers';
-import Loader from '@/src/components/Common/Loader';
+import {
+ MarketplaceQueryParams,
+ PlayerContextButtonClasses,
+ PlayerContextIconClasses,
+ PlayerContextIcons,
+ StatusIcons,
+} from '@/src/constants/marketplace';
import { ModelVersionSelect } from '../../Chat/ModelVersionSelect';
+import ContextMenu from '../../Common/ContextMenu';
import Tooltip from '../../Common/Tooltip';
import { ApplicationLogs } from '../ApplicationLogs';
+import { ApplicationCopyLink } from './ApplicationCopyLink';
import UnpublishIcon from '@/public/images/icons/unpublish.svg';
import IconUserUnshare from '@/public/images/icons/unshare-user.svg';
@@ -101,6 +115,8 @@ export const ApplicationDetailsFooter = ({
const { t } = useTranslation(Translation.Marketplace);
const dispatch = useAppDispatch();
+ const screenState = useScreenState();
+
const [isOpenLogs, setIsOpenLogs] = useState
();
const isCodeAppsEnabled = useAppSelector((state) =>
@@ -112,7 +128,12 @@ export const ApplicationDetailsFooter = ({
const isMyApp = isMyApplication(entity);
const isAdmin = useAppSelector(AuthSelectors.selectIsAdmin);
- const isPublicApp = isEntityIdPublic(entity);
+
+ const hasPublicId = isEntityIdPublic(entity);
+ const isPublicApp = isApplicationPublic(entity);
+
+ const isSmallScreen = screenState === ScreenState.MOBILE;
+
const Bookmark = installedModelIds.has(entity.reference)
? IconBookmarkFilled
: IconBookmark;
@@ -138,145 +159,318 @@ export const ApplicationDetailsFooter = ({
[setIsOpenLogs],
);
- const PlayerIcon = useMemo(() => {
- switch (playerStatus) {
- case SimpleApplicationStatus.DEPLOY:
- return IconPlayerPlay;
- case SimpleApplicationStatus.UNDEPLOY:
- return IconPlaystationSquare;
- case SimpleApplicationStatus.UPDATING:
- default:
- return Loader;
- }
- }, [playerStatus]);
-
- const handleUpdateFunctionStatus = () => {
+ const PlayerIcon = StatusIcons[playerStatus];
+ const PlayerContextIcon = PlayerContextIcons[playerStatus];
+
+ const handleUpdateFunctionStatus = useCallback(() => {
dispatch(
ApplicationActions.startUpdatingFunctionStatus({
id: entity.id,
status: getApplicationNextStatus(entity),
}),
);
- };
+ }, [dispatch, entity]);
const handleOpenUnshare = useCallback(
() => dispatch(ShareActions.setUnshareEntity(entity)),
[dispatch, entity],
);
+ const handleOpenSharing = useCallback(() => {
+ dispatch(
+ ShareActions.share({
+ featureType: FeatureType.Application,
+ resourceId: entity.id,
+ }),
+ );
+ }, [dispatch, entity.id]);
+
const isApplicationsSharingEnabled = useAppSelector((state) =>
SettingsSelectors.isFeatureEnabled(state, Feature.ApplicationsSharing),
);
+ const link = useMemo(
+ () =>
+ `${window.location.origin}/${PageType.Marketplace}?${MarketplaceQueryParams.model}=${entity.reference}`,
+ [entity.reference],
+ );
+
+ const handleCopy = useCallback(() => {
+ if (!navigator.clipboard) return;
+ navigator.clipboard.writeText(link);
+ dispatch(UIActions.showSuccessToast(t('Link copied!')));
+ }, [dispatch, link, t]);
+
+ const menuItems: DisplayMenuItemProps[] = useMemo(
+ () => [
+ {
+ name: t('Copy link'),
+ dataQa: 'application-copy-link',
+ display: isPublicApp && isSmallScreen,
+ Icon: IconLink,
+ onClick: (e: React.MouseEvent) => {
+ handleCopy();
+ e.preventDefault();
+ e.stopPropagation();
+ },
+ },
+ {
+ name: t(getFunctionTooltip(entity)),
+ dataQa: 'status-change',
+ display: isExecutable && isCodeAppsEnabled,
+ disabled: playerStatus === SimpleApplicationStatus.UPDATING,
+ Icon: PlayerContextIcon,
+ iconClassName: PlayerContextIconClasses[playerStatus],
+ onClick: (e: React.MouseEvent) => {
+ e.stopPropagation();
+ handleUpdateFunctionStatus();
+ },
+ },
+ {
+ name: t('Share'),
+ dataQa: 'share',
+ display: isMyApp && isApplicationsSharingEnabled && isSmallScreen,
+ Icon: IconUserShare,
+ onClick: (e: React.MouseEvent) => {
+ handleOpenSharing();
+ e.stopPropagation();
+ },
+ },
+ {
+ name: t('Unshare'),
+ dataQa: 'unshare',
+ display: !!entity.sharedWithMe && isApplicationsSharingEnabled,
+ Icon: IconUserUnshare,
+ onClick: (e: React.MouseEvent) => {
+ handleOpenUnshare();
+ e.stopPropagation();
+ },
+ },
+ {
+ name: t('Delete'),
+ dataQa: 'delete',
+ display: isMyApp,
+ disabled: isModifyDisabled,
+ Icon: IconTrashX,
+ onClick: (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onDelete?.(entity);
+ },
+ },
+ {
+ name: t('Publish'),
+ dataQa: 'publish',
+ display: isMyApp,
+ Icon: IconWorldShare,
+ onClick: (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onPublish?.(entity, PublishActions.ADD);
+ },
+ },
+ {
+ name: t('Unpublish'),
+ dataQa: 'unpublish',
+ display: hasPublicId,
+ Icon: UnpublishIcon,
+ onClick: (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onPublish?.(entity, PublishActions.DELETE);
+ },
+ },
+ {
+ name: t('Edit'),
+ dataQa: 'edit',
+ display: (isMyApp || !!canWrite) && !!onEdit,
+ disabled: isAppInDeployment,
+ Icon: IconEdit,
+ onClick: (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onEdit?.(entity);
+ },
+ },
+ {
+ name: t('Application logs'),
+ dataQa: 'app-logs',
+ display:
+ isExecutable && playerStatus === SimpleApplicationStatus.UNDEPLOY,
+ Icon: IconFileDescription,
+ onClick: (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ handleLogClick(entity.id);
+ },
+ },
+ ],
+ [
+ t,
+ isPublicApp,
+ isSmallScreen,
+ entity,
+ isExecutable,
+ isCodeAppsEnabled,
+ playerStatus,
+ PlayerContextIcon,
+ isMyApp,
+ isApplicationsSharingEnabled,
+ isModifyDisabled,
+ hasPublicId,
+ canWrite,
+ onEdit,
+ isAppInDeployment,
+ handleCopy,
+ handleUpdateFunctionStatus,
+ handleOpenSharing,
+ handleOpenUnshare,
+ onDelete,
+ onPublish,
+ handleLogClick,
+ ],
+ );
+
+ const hasBookmark = !isMyApp || !entity.sharedWithMe;
+ const countDisplayTrue = menuItems.filter((item) => item.display).length;
+ const menuItemsCount = hasBookmark ? countDisplayTrue + 1 : countDisplayTrue;
+
return (
-
+
- {isExecutable && isCodeAppsEnabled && (
-
-
-
- )}
- {!!entity.sharedWithMe && isApplicationsSharingEnabled && (
-
-
-
- )}
- {!entity.sharedWithMe &&
- (isMyApp ? (
-
-
-
- ) : (
-
-
-
- ))}
-
- {isApplicationId(entity.id) && (isMyApp || isPublicApp) && (
-
-
-
+ >
)}
- {(isMyApp || canWrite) && (
-
+ {hasBookmark && (
+
onEdit(entity)}
+ onClick={() => onBookmarkClick(entity)}
className="icon-button"
- data-qa="application-edit"
+ data-qa="application-bookmark"
>
-
+
)}
- {isExecutable &&
- playerStatus === SimpleApplicationStatus.UNDEPLOY && (
-
- handleLogClick(entity.id)}
- className="icon-button"
- data-qa="application-logs"
- >
-
-
-
- )}
-
+
{
const dispatch = useAppDispatch();
const screenState = useScreenState();
- const shareIconSize =
- screenState === ScreenState.MOBILE
- ? MOBILE_SHARE_ICON_SIZE
- : DESKTOP_SHARE_ICON_SIZE;
+ const { iconSize, shareIconSize } = HeaderIconSizes[screenState];
const isMyApp = isMyApplication(entity);
const isPublicApp = isApplicationPublic(entity);
@@ -61,7 +57,7 @@ export const ApplicationDetailsHeader = ({ entity }: Props) => {
return (
-
+
{
isCustomTooltip
entity={entity}
entityId={entity.id}
- size={screenState === ScreenState.MOBILE ? 48 : 96}
+ size={iconSize}
/>
@@ -104,16 +100,20 @@ export const ApplicationDetailsHeader = ({ entity }: Props) => {
- {isMyApp && isApplicationsSharingEnabled && (
-
-
- {t('Share')}
-
+ {isMyApp &&
+ isApplicationsSharingEnabled &&
+ screenState !== ScreenState.MOBILE && (
+
+
+ {t('Share')}
+
+ )}
+ {isPublicApp && screenState !== ScreenState.MOBILE && (
+
)}
- {isPublicApp && }
);
};
diff --git a/apps/chat/src/components/Marketplace/CardsList.tsx b/apps/chat/src/components/Marketplace/CardsList.tsx
index e609d3c8c..9273d8f66 100644
--- a/apps/chat/src/components/Marketplace/CardsList.tsx
+++ b/apps/chat/src/components/Marketplace/CardsList.tsx
@@ -9,7 +9,6 @@ import { PublishActions } from '@epam/ai-dial-shared';
interface CardsListProps {
entities: DialAIEntityModel[];
- isNotDesktop?: boolean;
title?: string;
className?: string;
onCardClick: (entity: DialAIEntityModel) => void;
@@ -22,7 +21,6 @@ interface CardsListProps {
export const CardsList = ({
entities,
- isNotDesktop,
title,
className,
onCardClick,
@@ -45,7 +43,6 @@ export const CardsList = ({
{
) : (
<>
-
-
+
{showOverlay &&
}
>
)}
diff --git a/apps/chat/src/components/Marketplace/TabRenderer.tsx b/apps/chat/src/components/Marketplace/TabRenderer.tsx
index 99980073a..41a33a053 100644
--- a/apps/chat/src/components/Marketplace/TabRenderer.tsx
+++ b/apps/chat/src/components/Marketplace/TabRenderer.tsx
@@ -17,7 +17,6 @@ import {
ApplicationActionType,
ApplicationType,
} from '@/src/types/applications';
-import { ScreenState } from '@/src/types/common';
import { DialAIEntityModel } from '@/src/types/models';
import { SharingType } from '@/src/types/share';
import { Translation } from '@/src/types/translation';
@@ -71,7 +70,6 @@ interface ResultsViewProps {
suggestedResults: DialAIEntityModel[];
selectedTab: MarketplaceTabs;
areAllFiltersEmpty: boolean;
- isNotDesktop: boolean;
onCardClick: (entity: DialAIEntityModel, isSuggested?: boolean) => void;
onPublish: (entity: DialAIEntityModel, action: PublishActions) => void;
onDelete: (entity: DialAIEntityModel) => void;
@@ -87,7 +85,6 @@ const ResultsView = ({
onPublish,
onDelete,
onEdit,
- isNotDesktop,
onBookmarkClick,
}: ResultsViewProps) => {
const { t } = useTranslation(Translation.Marketplace);
@@ -108,7 +105,6 @@ const ResultsView = ({
onPublish={onPublish}
onDelete={onDelete}
onEdit={onEdit}
- isNotDesktop={isNotDesktop}
onBookmarkClick={onBookmarkClick}
/>
{!entities.length && (
@@ -134,7 +130,6 @@ const ResultsView = ({
onPublish={onPublish}
onDelete={onDelete}
onEdit={onEdit}
- isNotDesktop={isNotDesktop}
onBookmarkClick={onBookmarkClick}
/>
>
@@ -149,7 +144,6 @@ const ResultsView = ({
onPublish={onPublish}
onDelete={onDelete}
onEdit={onEdit}
- isNotDesktop={isNotDesktop}
onBookmarkClick={onBookmarkClick}
/>
);
@@ -208,11 +202,7 @@ const getDeleteConfirmationText = (
return deleteConfirmationText[action];
};
-interface TabRendererProps {
- screenState: ScreenState;
-}
-
-export const TabRenderer = ({ screenState }: TabRendererProps) => {
+export const TabRenderer = () => {
const { t } = useTranslation(Translation.Marketplace);
const dispatch = useAppDispatch();
@@ -441,7 +431,6 @@ export const TabRenderer = ({ screenState }: TabRendererProps) => {
onPublish={handleSetPublishEntity}
onDelete={handleDelete}
onEdit={handleEditApplication}
- isNotDesktop={screenState !== ScreenState.DESKTOP}
onBookmarkClick={handleBookmarkClick}
/>
diff --git a/apps/chat/src/constants/marketplace.ts b/apps/chat/src/constants/marketplace.ts
index ba5c4967f..90b4e25c5 100644
--- a/apps/chat/src/constants/marketplace.ts
+++ b/apps/chat/src/constants/marketplace.ts
@@ -1,5 +1,14 @@
-import { ApplicationType } from '../types/applications';
-import { EntityType } from '../types/common';
+import { IconPlayerPlay, IconPlaystationSquare } from '@tabler/icons-react';
+
+import {
+ ApplicationType,
+ SimpleApplicationStatus,
+} from '../types/applications';
+import { EntityType, ScreenState } from '../types/common';
+
+import Loader from '../components/Common/Loader';
+
+import LoaderIcon from '@/public/images/icons/loader.svg';
export enum MarketplaceQueryParams {
fromConversation = 'fromConversation',
@@ -59,3 +68,43 @@ export const ApplicationTypeToSourceType = {
[ApplicationType.CUSTOM_APP]: SourceType.MyCustomApps,
[ApplicationType.MINDMAP]: SourceType.MyMindMaps,
};
+
+interface IconSize {
+ iconSize: number;
+ shareIconSize: number;
+}
+
+export const CardIconSizes: Record
= {
+ [ScreenState.DESKTOP]: { iconSize: 80, shareIconSize: 30 },
+ [ScreenState.TABLET]: { iconSize: 48, shareIconSize: 20 },
+ [ScreenState.MOBILE]: { iconSize: 40, shareIconSize: 16 },
+};
+
+export const HeaderIconSizes: Record = {
+ [ScreenState.DESKTOP]: { iconSize: 96, shareIconSize: 30 },
+ [ScreenState.TABLET]: { iconSize: 96, shareIconSize: 30 },
+ [ScreenState.MOBILE]: { iconSize: 48, shareIconSize: 20 },
+};
+
+export const PlayerContextIconClasses = {
+ [SimpleApplicationStatus.DEPLOY]: 'text-accent-secondary',
+ [SimpleApplicationStatus.UNDEPLOY]: 'text-error',
+ [SimpleApplicationStatus.UPDATING]: 'animate-spin-steps',
+};
+
+export const PlayerContextButtonClasses = {
+ [SimpleApplicationStatus.DEPLOY]: 'button-accent-secondary',
+ [SimpleApplicationStatus.UNDEPLOY]: 'button-error',
+ [SimpleApplicationStatus.UPDATING]: '',
+};
+
+export const StatusIcons = {
+ [SimpleApplicationStatus.DEPLOY]: IconPlayerPlay,
+ [SimpleApplicationStatus.UNDEPLOY]: IconPlaystationSquare,
+ [SimpleApplicationStatus.UPDATING]: Loader,
+};
+
+export const PlayerContextIcons = {
+ ...StatusIcons,
+ [SimpleApplicationStatus.UPDATING]: LoaderIcon,
+};