Skip to content

Commit

Permalink
feat(chat): implement direct application links (Issue #2950) (#2957)
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyaBondar authored Jan 21, 2025
1 parent fc3a151 commit 8927ae6
Show file tree
Hide file tree
Showing 16 changed files with 310 additions and 57 deletions.
8 changes: 1 addition & 7 deletions apps/chat/src/components/Chat/ShareModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ export default function ShareModalView() {
const { t } = useTranslation(Translation.SideBar);
const dispatch = useAppDispatch();

const copyButtonRef = useRef<HTMLButtonElement>(null);
const [urlCopied, setUrlCopied] = useState(false);
const [urlWasCopied, setUrlWasCopied] = useState(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();

const [editAccess, setEditAccess] = useState(false);
Expand Down Expand Up @@ -152,12 +150,9 @@ export default function ShareModalView() {
timeoutRef.current = setTimeout(() => {
setUrlCopied(false);
}, 2000);
if (!urlWasCopied) {
setUrlWasCopied(true);
}
});
},
[url, urlWasCopied],
[url],
);

useEffect(() => () => clearTimeout(timeoutRef.current), []);
Expand Down Expand Up @@ -209,7 +204,6 @@ export default function ShareModalView() {
<button
className="outline-none"
onClick={handleCopy}
ref={copyButtonRef}
data-qa="copy-link"
>
<IconCopy
Expand Down
7 changes: 6 additions & 1 deletion apps/chat/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { UIActions } from '@/src/store/ui/ui.reducers';

import Loader from './Common/Loader';

const removeQueryString = (url: string) => url.split('?')[0];

export default function Layout({
children,
settings,
Expand Down Expand Up @@ -55,7 +57,10 @@ export default function Layout({
);

const shouldOverlayLogin = isOverlay && shouldLogin;
const handleStartRedirecting = useCallback(() => setLoading(true), []);

const handleStartRedirecting = useCallback((url: string) => {
setLoading(removeQueryString(url) !== window.location.pathname);
}, []);
const handleStopRedirecting = useCallback(() => setLoading(false), []);

// EFFECTS --------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { IconCheck, IconLink } from '@tabler/icons-react';
import { MouseEvent, useCallback, useMemo, useRef, useState } from 'react';

import { useTranslation } from 'next-i18next';

import { PageType } from '@/src/types/common';
import { DialAIEntityModel } from '@/src/types/models';
import { Translation } from '@/src/types/translation';

import { MarketplaceQueryParams } from '@/src/constants/marketplace';

interface ApplicationCopyLinkProps {
entity: DialAIEntityModel;
}

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) {
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],
);
const handleCopy = useCallback(
(e: MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
e.stopPropagation();
if (!navigator.clipboard) return;

navigator.clipboard.writeText(link).then(() => {
setUrlCopied(true);
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
setUrlCopied(false);
}, 2000);
});
},
[link],
);

return (
<>
{urlCopied ? (
<div className={TRIGGER_CLASS}>
<IconCheck size={ICON_SIZE} />
<span>{t('Copied!')}</span>
</div>
) : (
<a
className={TRIGGER_CLASS}
onClick={handleCopy}
data-qa="copy-link"
href={link}
>
<IconLink size={ICON_SIZE} />
<span>{t('Copy link')}</span>
</a>
)}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useTranslation } from 'next-i18next';

import classNames from 'classnames';

import { isApplicationPublic } from '@/src/utils/app/application';
import { getRootId } from '@/src/utils/app/id';

import { FeatureType } from '@/src/types/common';
Expand All @@ -19,6 +20,7 @@ import { FunctionStatusIndicator } from '@/src/components/Marketplace/FunctionSt

import { ModelIcon } from '../../Chatbar/ModelIcon';
import { ApplicationTopic } from '../ApplicationTopic';
import { ApplicationCopyLink } from './ApplicationCopyLink';

import { Feature } from '@epam/ai-dial-shared';

Expand All @@ -34,6 +36,7 @@ export const ApplicationDetailsHeader = ({ entity, isMobileView }: Props) => {
const isMyApp = entity.id.startsWith(
getRootId({ featureType: FeatureType.Application }),
);
const isPublicApp = isApplicationPublic(entity);
const handleOpenSharing: MouseEventHandler<HTMLButtonElement> =
useCallback(() => {
dispatch(
Expand Down Expand Up @@ -171,6 +174,7 @@ export const ApplicationDetailsHeader = ({ entity, isMobileView }: Props) => {
<span>{t('Share')}</span>
</button>
)}
{isPublicApp && <ApplicationCopyLink entity={entity} />}
</header>
);
};
17 changes: 0 additions & 17 deletions apps/chat/src/components/Marketplace/Marketplace.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { FloatingOverlay } from '@floating-ui/react';
import { useEffect } from 'react';

import { useSearchParams } from 'next/navigation';
import { useRouter } from 'next/router';

import { useScreenState } from '@/src/hooks/useScreenState';
Expand All @@ -16,11 +15,6 @@ import {
import { ModelsSelectors } from '@/src/store/models/models.reducers';
import { UISelectors } from '@/src/store/ui/ui.reducers';

import {
MarketplaceQueryParams,
MarketplaceTabs,
} from '@/src/constants/marketplace';

import { Spinner } from '@/src/components/Common/Spinner';
import { TabRenderer } from '@/src/components/Marketplace/TabRenderer';

Expand All @@ -30,7 +24,6 @@ export const Marketplace = () => {
const dispatch = useAppDispatch();

const router = useRouter();
const searchParams = useSearchParams();

const isFilterbarOpen = useAppSelector(
UISelectors.selectShowMarketplaceFilterbar,
Expand All @@ -46,16 +39,6 @@ export const Marketplace = () => {
const showOverlay =
(isFilterbarOpen || isProfileOpen) && screenState === ScreenState.MOBILE;

useEffect(() => {
dispatch(
MarketplaceActions.setSelectedTab(
searchParams.get(MarketplaceQueryParams.fromConversation)
? MarketplaceTabs.MY_APPLICATIONS
: MarketplaceTabs.HOME,
),
);
}, [dispatch, searchParams]);

useEffect(() => {
if (applyModelStatus === UploadStatus.LOADED) {
dispatch(
Expand Down
8 changes: 4 additions & 4 deletions apps/chat/src/components/Marketplace/MarketplaceBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,24 @@ import lightBanner from '@/public/images/banners/welcome-light.jpg';
const bannerText = {
title: {
[MarketplaceTabs.HOME]: 'Welcome to DIAL Marketplace',
[MarketplaceTabs.MY_APPLICATIONS]: 'My workspace',
[MarketplaceTabs.MY_WORKSPACE]: 'My workspace',
},
subtitle: {
[MarketplaceTabs.HOME]:
'Explore our AI offerings with your data and see how they boost your productivity!',
[MarketplaceTabs.MY_APPLICATIONS]:
[MarketplaceTabs.MY_WORKSPACE]:
'Here you can manage the AI agents used in your daily work',
},
};

const getBannerSrc = (theme: string, tab: MarketplaceTabs) => {
if (theme === 'dark') {
return tab === MarketplaceTabs.MY_APPLICATIONS
return tab === MarketplaceTabs.MY_WORKSPACE
? darkMyAppsBanner.src
: darkBanner.src;
}

return tab === MarketplaceTabs.MY_APPLICATIONS
return tab === MarketplaceTabs.MY_WORKSPACE
? lightMyAppsBanner.src
: lightBanner.src;
};
Expand Down
25 changes: 10 additions & 15 deletions apps/chat/src/components/Marketplace/MarketplaceFilterbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useTranslation } from 'next-i18next';

import classNames from 'classnames';

import { EntityType } from '@/src/types/common';
import { MarketplaceFilters } from '@/src/types/marketplace';
import { Translation } from '@/src/types/translation';

import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
Expand All @@ -22,7 +22,11 @@ import {
import { ModelsSelectors } from '@/src/store/models/models.reducers';
import { UISelectors } from '@/src/store/ui/ui.reducers';

import { FilterTypes, MarketplaceTabs } from '@/src/constants/marketplace';
import {
ENTITY_TYPES,
FilterTypes,
MarketplaceTabs,
} from '@/src/constants/marketplace';

import Tooltip from '../Common/Tooltip';

Expand Down Expand Up @@ -68,10 +72,7 @@ const FilterItem = ({
interface FilterSectionProps {
sectionName: string;
openedSections: Record<FilterTypes, boolean>;
selectedFilters: {
Type: string[];
Topics: string[];
};
selectedFilters: MarketplaceFilters;
filterValues: string[];
filterType: FilterTypes;
onToggleFilterSection: (filterType: FilterTypes) => void;
Expand Down Expand Up @@ -129,12 +130,6 @@ const FilterSection = ({
);
};

const entityTypes = [
EntityType.Model,
EntityType.Assistant,
EntityType.Application,
];

interface ActionButtonProps {
isOpen: boolean;
onClick: () => void;
Expand Down Expand Up @@ -220,7 +215,7 @@ export const MarketplaceFilterbar = () => {
);

const handleMyAppsClick = useCallback(
() => handleChangeTab(MarketplaceTabs.MY_APPLICATIONS),
() => handleChangeTab(MarketplaceTabs.MY_WORKSPACE),
[handleChangeTab],
);

Expand Down Expand Up @@ -254,15 +249,15 @@ export const MarketplaceFilterbar = () => {
onClick={handleMyAppsClick}
caption={t('My workspace')}
Icon={IconHome2}
selected={selectedTab === MarketplaceTabs.MY_APPLICATIONS}
selected={selectedTab === MarketplaceTabs.MY_WORKSPACE}
dataQa="my-applications"
/>
</div>
{showFilterbar && (
<>
<FilterSection
sectionName={t('Type')}
filterValues={entityTypes}
filterValues={ENTITY_TYPES}
openedSections={openedSections}
selectedFilters={selectedFilters}
filterType={FilterTypes.ENTITY_TYPE}
Expand Down
2 changes: 1 addition & 1 deletion apps/chat/src/components/Marketplace/SearchHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export const SearchHeader = ({
className="w-full rounded border border-primary bg-transparent py-2.5 pl-[38px] pr-3 leading-4 outline-none placeholder:text-secondary focus-visible:border-accent-primary"
/>
</div>
{selectedTab === MarketplaceTabs.MY_APPLICATIONS && (
{selectedTab === MarketplaceTabs.MY_WORKSPACE && (
<AddAppButton menuItems={menuItems} />
)}
</div>
Expand Down
6 changes: 3 additions & 3 deletions apps/chat/src/components/Marketplace/TabRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,12 @@ export const TabRenderer = ({ screenState }: TabRendererProps) => {
installedModelIds.has(entity.reference);

const entitiesForTab =
selectedTab === MarketplaceTabs.MY_APPLICATIONS
selectedTab === MarketplaceTabs.MY_WORKSPACE
? filteredEntities.filter(isInstalledModel)
: filteredEntities;

const shouldSuggest =
selectedTab === MarketplaceTabs.MY_APPLICATIONS && isSomeFilterNotEmpty;
selectedTab === MarketplaceTabs.MY_WORKSPACE && isSomeFilterNotEmpty;

const groupedEntities = groupModelsAndSaveOrder(
entitiesForTab.concat(shouldSuggest ? filteredEntities : []),
Expand Down Expand Up @@ -481,7 +481,7 @@ export const TabRenderer = ({ screenState }: TabRendererProps) => {
onEdit={handleEditApplication}
onBookmarkClick={handleBookmarkClick}
allEntities={allModels}
isMyAppsTab={selectedTab === MarketplaceTabs.MY_APPLICATIONS}
isMyAppsTab={selectedTab === MarketplaceTabs.MY_WORKSPACE}
isSuggested={detailsModel.isSuggested}
/>
)}
Expand Down
17 changes: 15 additions & 2 deletions apps/chat/src/constants/marketplace.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { EntityType } from '../types/common';

export enum MarketplaceQueryParams {
fromConversation = 'fromConversation',
model = 'model',
tab = 'tab',
types = 'types',
topics = 'topics',
search = 'search',
}

export enum FilterTypes {
Expand All @@ -10,11 +17,17 @@ export enum FilterTypes {
}

export enum MarketplaceTabs {
HOME = 'HOME',
MY_APPLICATIONS = 'MY_APPLICATIONS',
HOME = 'marketplace',
MY_WORKSPACE = 'workspace',
}

export enum DeleteType {
DELETE,
REMOVE,
}

export const ENTITY_TYPES = [
EntityType.Model,
EntityType.Assistant,
EntityType.Application,
];
2 changes: 2 additions & 0 deletions apps/chat/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { FilesEpics } from './files/files.epics';
import { filesSlice } from './files/files.reducers';
import { ImportExportEpics } from './import-export/importExport.epics';
import { importExportSlice } from './import-export/importExport.reducers';
import { MarketplaceEpics } from './marketplace/marketplace.epics';
import { marketplaceSlice } from './marketplace/marketplace.reducers';
import { MigrationEpics } from './migration/migration.epics';
import { migrationSlice } from './migration/migration.reducers';
Expand Down Expand Up @@ -68,6 +69,7 @@ export const rootEpic = combineEpics(
ApplicationEpics,
CodeEditorEpics,
ChatEpics,
MarketplaceEpics,
);

const reducer = combineReducers({
Expand Down
Loading

0 comments on commit 8927ae6

Please sign in to comment.