Skip to content

Commit

Permalink
fix(chat): fix card design, add copy link and share in context menu, …
Browse files Browse the repository at this point in the history
…correct mobile icon size (Issues #2943, #2991) (#3025)
  • Loading branch information
Derikyan authored Jan 31, 2025
1 parent b9c5030 commit 41708a1
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 256 deletions.
5 changes: 4 additions & 1 deletion apps/chat/src/components/Chat/ModelVersionSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ export const ModelVersionSelect = ({
onClick={stopBubbling}
>
{showVersionPrefix && <VersionPrefix />}
<span className="truncate" data-qa="version">
<span
className="max-w-full overflow-hidden truncate whitespace-nowrap"
data-qa="version"
>
{currentEntity.version || currentEntity.id}
</span>
<ChevronDownIcon
Expand Down
57 changes: 12 additions & 45 deletions apps/chat/src/components/Chat/TalkTo/TalkToCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {
IconFileDescription,
IconPencilMinus,
IconPlayerPlay,
IconPlaystationSquare,
IconTrashX,
IconUserShare,
IconWorldShare,
Expand Down Expand Up @@ -31,7 +29,7 @@ import {
SimpleApplicationStatus,
} from '@/src/types/applications';
import { Conversation } from '@/src/types/chat';
import { FeatureType, ScreenState } from '@/src/types/common';
import { FeatureType } from '@/src/types/common';
import { DisplayMenuItemProps } from '@/src/types/menu';
import { DialAIEntityModel } from '@/src/types/models';
import { Translation } from '@/src/types/translation';
Expand All @@ -44,6 +42,11 @@ import { SettingsSelectors } from '@/src/store/settings/settings.reducers';
import { ShareActions } from '@/src/store/share/share.reducers';

import { REPLAY_AS_IS_MODEL } from '@/src/constants/chat';
import {
CardIconSizes,
PlayerContextIconClasses,
PlayerContextIcons,
} from '@/src/constants/marketplace';

import { ModelVersionSelect } from '@/src/components/Chat/ModelVersionSelect';
import { PlaybackIcon } from '@/src/components/Chat/Playback/PlaybackIcon';
Expand All @@ -56,18 +59,9 @@ import { FunctionStatusIndicator } from '@/src/components/Marketplace/FunctionSt

import ShareIcon from '../../Common/ShareIcon';

import LoaderIcon from '@/public/images/icons/loader.svg';
import IconUserUnshare from '@/public/images/icons/unshare-user.svg';
import { Feature } from '@epam/ai-dial-shared';

const DESKTOP_ICON_SIZE = 80;
const TABLET_ICON_SIZE = 48;
const MOBILE_ICON_SIZE = 40;

const MOBILE_SHARE_ICON_SIZE = 16;
const TABLET_SHARE_ICON_SIZE = 20;
const DESKTOP_SHARE_ICON_SIZE = 30;

const getPlayerCaption = (entity: DialAIEntityModel) => {
switch (entity.functionStatus) {
case ApplicationStatus.DEPLOYED:
Expand Down Expand Up @@ -135,6 +129,8 @@ export const TalkToCard = ({
SettingsSelectors.isFeatureEnabled(state, Feature.ApplicationsSharing),
);

const { iconSize, shareIconSize } = CardIconSizes[screenState];

const versionsToSelect = useMemo(() => {
return allModels.filter(
(model) =>
Expand All @@ -155,17 +151,7 @@ export const TalkToCard = ({
const isModifyDisabled = isApplicationStatusUpdating(entity);
const playerStatus = getApplicationSimpleStatus(entity);

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(
Expand Down Expand Up @@ -207,14 +193,8 @@ export const TalkToCard = ({
(isAdmin || isMyEntity) &&
!!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();
Expand Down Expand Up @@ -292,7 +272,7 @@ export const TalkToCard = ({
isAdmin,
isMyEntity,
isCodeAppsEnabled,
PlayerIcon,
PlayerContextIcon,
canWrite,
onEdit,
isApplicationsSharingEnabled,
Expand All @@ -307,23 +287,10 @@ export const TalkToCard = ({
],
);

const iconSize =
screenState === ScreenState.DESKTOP
? DESKTOP_ICON_SIZE
: screenState === ScreenState.TABLET
? TABLET_ICON_SIZE
: MOBILE_ICON_SIZE;
const isOldReplay =
entity.id === REPLAY_AS_IS_MODEL &&
isOldConversationReplay(conversation.replay);

const shareIconSize =
screenState === ScreenState.MOBILE
? MOBILE_SHARE_ICON_SIZE
: screenState === ScreenState.TABLET
? TABLET_SHARE_ICON_SIZE
: DESKTOP_SHARE_ICON_SIZE;

return (
<div
onClick={() => {
Expand Down
57 changes: 14 additions & 43 deletions apps/chat/src/components/Marketplace/ApplicationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import {
IconBookmarkFilled,
IconFileDescription,
IconPencilMinus,
IconPlayerPlay,
IconPlaystationSquare,
IconTrashX,
IconUserShare,
IconWorldShare,
Expand All @@ -24,15 +22,14 @@ import {
isExecutableApp,
} from '@/src/utils/app/application';
import { isMyApplication } from '@/src/utils/app/id';
import { isMediumScreen } from '@/src/utils/app/mobile';
import { isEntityIdPublic } from '@/src/utils/app/publications';
import { canWriteSharedWithMe } from '@/src/utils/app/share';

import {
ApplicationStatus,
SimpleApplicationStatus,
} from '@/src/types/applications';
import { FeatureType, ScreenState } from '@/src/types/common';
import { FeatureType } from '@/src/types/common';
import { DisplayMenuItemProps } from '@/src/types/menu';
import { DialAIEntityModel } from '@/src/types/models';
import { Translation } from '@/src/types/translation';
Expand All @@ -44,6 +41,12 @@ 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 {
CardIconSizes,
PlayerContextIconClasses,
PlayerContextIcons,
} from '@/src/constants/marketplace';

import { ModelIcon } from '@/src/components/Chatbar/ModelIcon';
import ContextMenu from '@/src/components/Common/ContextMenu';
import { EntityMarkdownDescription } from '@/src/components/Common/MarkdownDescription';
Expand All @@ -54,27 +57,18 @@ import ShareIcon from '../Common/ShareIcon';
import Tooltip from '../Common/Tooltip';
import { ApplicationLogs } from './ApplicationLogs';

import LoaderIcon from '@/public/images/icons/loader.svg';
import UnpublishIcon from '@/public/images/icons/unpublish.svg';
import IconUserUnshare from '@/public/images/icons/unshare-user.svg';
import { Feature, PublishActions } from '@epam/ai-dial-shared';

const DESKTOP_ICON_SIZE = 80;
const SMALL_ICON_SIZE = 48;

// TODO uncomment in #2943
// const MOBILE_SHARE_ICON_SIZE = 16;
const TABLET_SHARE_ICON_SIZE = 20;
const DESKTOP_SHARE_ICON_SIZE = 30;

interface CardFooterProps {
entity: DialAIEntityModel;
}

const CardFooter = ({ entity }: CardFooterProps) => {
return (
<>
<EntityMarkdownDescription className="mt-3 line-clamp-2 text-ellipsis text-sm leading-[18px] text-secondary xl:hidden">
<EntityMarkdownDescription className="mt-3 hidden text-ellipsis text-sm leading-[18px] text-secondary md:line-clamp-2 xl:hidden">
{getModelShortDescription(entity)}
</EntityMarkdownDescription>
<div className="flex flex-col gap-2 pt-3 md:pt-4">
Expand Down Expand Up @@ -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;
Expand All @@ -119,7 +112,6 @@ interface ApplicationCardProps {

export const ApplicationCard = ({
entity,
isNotDesktop,
onClick,
onDelete,
onEdit,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -302,7 +275,7 @@ export const ApplicationCard = ({
isAdmin,
isMyApp,
isCodeAppsEnabled,
PlayerIcon,
PlayerContextIcon,
canWrite,
onEdit,
isApplicationsSharingEnabled,
Expand All @@ -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;
Expand All @@ -326,7 +297,7 @@ export const ApplicationCard = ({
<>
<div
onClick={() => 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"
>
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ReturnType<typeof setTimeout>>();
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<HTMLAnchorElement>) => {
Expand All @@ -44,23 +56,25 @@ export function ApplicationCopyLink({ entity }: ApplicationCopyLinkProps) {
);

return (
<>
<Tooltip
tooltip={hasTooltip ? t(urlCopied ? 'Copied!' : 'Copy link') : undefined}
>
{urlCopied ? (
<div className={TRIGGER_CLASS}>
<IconCheck size={ICON_SIZE} />
<span>{t('Copied!')}</span>
<div className={classNames(TRIGGER_CLASS, className)}>
<IconCheck size={size} />
{withText && <span>{t('Copied!')}</span>}
</div>
) : (
<a
className={TRIGGER_CLASS}
className={classNames(TRIGGER_CLASS, className)}
onClick={handleCopy}
data-qa="copy-link"
href={link}
>
<IconLink size={ICON_SIZE} />
<span>{t('Copy link')}</span>
<IconLink size={size} />
{withText && <span>{t('Copy link')}</span>}
</a>
)}
</>
</Tooltip>
);
}
Loading

0 comments on commit 41708a1

Please sign in to comment.