Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chat): implement direct application links (Issue #2950) #2957

Merged
merged 15 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion apps/chat/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function Layout({
);

const shouldOverlayLogin = isOverlay && shouldLogin;
const handleStartRedirecting = useCallback(() => setLoading(true), []);
const handleStartRedirecting = useCallback(() => setLoading(false), []); // TODO: how show loader for page switching?
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
Loading