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): support topics (Issue #2086) #2269

Merged
merged 26 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5306ff6
feat(chat): add topics
Alexander-Kezik Oct 2, 2024
21ef2b1
remove Divider component
Alexander-Kezik Oct 2, 2024
f9ddab5
remove css layer
Alexander-Kezik Oct 2, 2024
e44dcfb
fix docker build
Alexander-Kezik Oct 2, 2024
a49ff92
Merge branch 'development' into feat/topics
Alexander-Kezik Oct 2, 2024
c9d607c
mock topics
Alexander-Kezik Oct 2, 2024
a4e7df1
refactor
Alexander-Kezik Oct 2, 2024
8f79162
make field options, register topicColors field
Alexander-Kezik Oct 2, 2024
e96f414
getTopicColors
Alexander-Kezik Oct 2, 2024
4fda56a
Merge branch 'development' into feat/topics
Alexander-Kezik Oct 2, 2024
4995c4c
feat(chat): add topics and capabilities fields to custom apps (Issue …
Alexander-Kezik Oct 2, 2024
361b3d4
resolve comments + add topic filters
Alexander-Kezik Oct 2, 2024
c9c9580
fix colors
Alexander-Kezik Oct 2, 2024
c6cc791
Merge branch 'development' into feat/topics
Alexander-Kezik Oct 2, 2024
2f5ac94
add topics filters
Alexander-Kezik Oct 2, 2024
d079b5e
fix topics filter
Alexander-Kezik Oct 2, 2024
1867bb6
fallback topics colors
IlyaBondar Oct 3, 2024
74744aa
topic saving, displaying and filtering
IlyaBondar Oct 3, 2024
336d920
Merge branch 'development' into feat/topics
IlyaBondar Oct 3, 2024
a481524
topics displaying
IlyaBondar Oct 3, 2024
60f461a
Merge branch 'feat/topics' of https://github.com/epam/ai-dial-chat in…
IlyaBondar Oct 3, 2024
1512727
Merge branch 'development' into feat/topics
IlyaBondar Oct 3, 2024
5e00471
Update package-lock.json
IlyaBondar Oct 3, 2024
f5246ec
edit of topics
IlyaBondar Oct 3, 2024
e6ae71f
uniq and sort
IlyaBondar Oct 3, 2024
267940a
format fix
IlyaBondar Oct 3, 2024
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
3 changes: 3 additions & 0 deletions apps/chat/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,6 @@ ANNOUNCEMENT_HTML_MESSAGE='Welcome to <a href="about:blank" target="_blank">AI D

## Overlay
NEXT_PUBLIC_OVERLAY_HOST="localhost:3000"

## Marketplace
TOPICS="Business,Development,User Experience,Analysis,SQL,SDLC,Talk-To-Your-Data,RAG,Text Generation,Image Generation,Image Recognition"
1 change: 1 addition & 0 deletions apps/chat/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ declare global {
STORAGE_TYPE?: string;
MAX_PROMPT_TOKENS_DEFAULT_PERCENT?: string;
MAX_PROMPT_TOKENS_DEFAULT_VALUE?: string;
TOPICS?: string;

NEXTAUTH_URL?: string;
NEXTAUTH_SECRET?: string;
Expand Down
118 changes: 112 additions & 6 deletions apps/chat/src/components/Common/ApplicationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
IconWorldShare,
IconX,
} from '@tabler/icons-react';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';

import { useTranslation } from 'next-i18next';
Expand All @@ -13,10 +13,11 @@ import classNames from 'classnames';

import { notAllowedSymbols } from '@/src/utils/app/file';
import { getFolderIdFromEntityId } from '@/src/utils/app/folders';
import { getTopicColors } from '@/src/utils/app/style-helpers';
import { ApiUtils } from '@/src/utils/server/api';

import { CustomApplicationModel } from '@/src/types/applications';
import { EntityType } from '@/src/types/common';
import { DropdownSelectorOption, EntityType } from '@/src/types/common';
import { ModalState } from '@/src/types/modal';
import { DialAIEntityFeatures } from '@/src/types/models';
import { SharingType } from '@/src/types/share';
Expand All @@ -28,6 +29,7 @@ import {
} from '@/src/store/application/application.reducers';
import { FilesSelectors } from '@/src/store/files/files.reducers';
import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
import { SettingsSelectors } from '@/src/store/settings/settings.reducers';

import { DEFAULT_VERSION } from '@/src/constants/public';

Expand All @@ -36,6 +38,7 @@ import Modal from '@/src/components/Common/Modal';
import { PublishModal } from '../Chat/Publish/PublishWizard';
import { CustomLogoSelect } from '../Settings/CustomLogoSelect';
import { ConfirmDialog } from './ConfirmDialog';
import { DropdownSelector } from './DropdownSelector';
import { MultipleComboBox } from './MultipleComboBox';
import { Spinner } from './Spinner';
import Tooltip from './Tooltip';
Expand All @@ -48,6 +51,8 @@ interface FormData {
description: string;
version: string;
iconUrl: string;
topics: string[];
// capabilities: string[];
inputAttachmentTypes: string[];
maxInputAttachments: number | undefined;
completionUrl: string;
Expand Down Expand Up @@ -106,6 +111,7 @@ const ApplicationDialogView: React.FC<Props> = ({
const dispatch = useAppDispatch();

const files = useAppSelector(FilesSelectors.selectFiles);
const allTopics = useAppSelector(SettingsSelectors.selectTopics);

const [deleteLogo, setDeleteLogo] = useState(false);
const [localLogoFile, setLocalLogoFile] = useState<string | undefined>();
Expand All @@ -115,12 +121,29 @@ const ApplicationDialogView: React.FC<Props> = ({
const [featuresInput, setFeaturesInput] = useState(
safeStringify(selectedApplication?.features),
);
const [topics, setTopics] = useState<string[]>([]);
// const [capabilities, setCapabilities] = useState<string[]>([]);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isPublishing, setIsPublishing] = useState(false);
const [maxInputAttachmentsValue, setMaxInputAttachmentsValue] = useState(
selectedApplication?.maxInputAttachments,
);

const topicOptions = useMemo(
() =>
allTopics.map((value) => ({
value,
label: value,
...getTopicColors(value),
})),
[allTopics],
);

const selectedOptions = useMemo(
() => topicOptions.filter((op) => topics.includes(op.value)),
[topicOptions, topics],
);

const inputClassName = 'input-form input-invalid peer mx-0';
const applicationToPublish = selectedApplication
? {
Expand Down Expand Up @@ -224,11 +247,17 @@ const ApplicationDialogView: React.FC<Props> = ({
setLocalLogoFile(selectedApplication.iconUrl);
setValue('iconUrl', selectedApplication.iconUrl);
}
setTopics(selectedApplication.topics ?? []);
setValue('topics', selectedApplication.topics ?? []);
} else {
setInputAttachmentTypes([]);
setValue('inputAttachmentTypes', []);
setLocalLogoFile(undefined);
setValue('iconUrl', '');
setTopics([]);
setValue('topics', []);
// setCapabilities([]);
// setValue('capabilities', []);
}
}, [isEdit, selectedApplication, setValue]);

Expand Down Expand Up @@ -265,6 +294,24 @@ const ApplicationDialogView: React.FC<Props> = ({
}
};

const handleChangeTopics = useCallback(
(option: readonly DropdownSelectorOption[]) => {
const values = option.map((option) => option.value);
setTopics(values);
setValue('topics', values);
},
[setValue],
);

// const handleChangeCapabilities = useCallback(
// (option: readonly DropdownSelectorOption[]) => {
// const values = option.map((option) => option.value);
// setCapabilities(values);
// setValue('capabilities', values);
// },
// [setValue],
// );

const handleChangeHandlerAttachments = (
event: React.ChangeEvent<HTMLInputElement>,
) => {
Expand All @@ -287,6 +334,8 @@ const ApplicationDialogView: React.FC<Props> = ({
type: EntityType.Application,
isDefault: false,
folderId: '',
topics,
// capabilities,
};

if (
Expand Down Expand Up @@ -432,6 +481,62 @@ const ApplicationDialogView: React.FC<Props> = ({
)}
</div>

<div className="flex flex-col">
<label
className="mb-1 flex text-xs text-secondary"
htmlFor="applicationIcon"
>
{t('Topics')}
</label>
<Controller
name="topics"
control={control}
render={({ field: { ref: _ref, ...restField } }) => (
<DropdownSelector
{...restField}
placeholder={t('Select one or more topics')}
onChange={handleChangeTopics}
options={topicOptions}
values={selectedOptions}
/>
)}
/>
{!localLogoFile && errors.iconUrl && (
<span className="text-xxs text-error peer-invalid:peer-[.submitted]:mb-1">
{errors.iconUrl.message}
</span>
)}
</div>

{/* <div className="flex flex-col">
<label
className="mb-1 flex text-xs text-secondary"
htmlFor="applicationIcon"
>
{t('Capabilities')}
</label>
<Controller
name="capabilities"
control={control}
render={({ field: { ref: _ref, ...restField } }) => (
<DropdownSelector
{...restField}
placeholder={t('Select one or more capabilities')}
onChange={handleChangeCapabilities}
options={[
{ value: 'test', label: 'Ocean' },
{ value: 'test1', label: 'Ocean' },
]}
/>
)}
/>
{!localLogoFile && errors.iconUrl && (
<span className="text-xxs text-error peer-invalid:peer-[.submitted]:mb-1">
{errors.iconUrl.message}
</span>
)}
</div> */}

<div className="flex flex-col">
<label
className="mb-1 flex items-center gap-1 text-xs text-secondary"
Expand Down Expand Up @@ -616,9 +721,8 @@ const ApplicationDialogView: React.FC<Props> = ({
type="text"
defaultValue={selectedApplication?.completionUrl}
className={classNames(
errors.completionUrl
? 'border-error hover:border-error focus:border-error'
: '',
errors.completionUrl &&
'border-error hover:border-error focus:border-error',
inputClassName,
)}
placeholder={t('Type completion URL') || ''}
Expand Down Expand Up @@ -697,7 +801,9 @@ const ApplicationDialogView: React.FC<Props> = ({
type={SharingType.Application}
isOpen={isPublishing}
onClose={handlePublishClose}
publishAction={PublishActions.ADD}
publishAction={
isPublishing ? PublishActions.ADD : PublishActions.DELETE
}
/>
)}
</>
Expand Down
150 changes: 150 additions & 0 deletions apps/chat/src/components/Common/DropdownSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { IconX } from '@tabler/icons-react';
import Select, { components } from 'react-select';

import { useTranslation } from 'next-i18next';

import { DropdownSelectorOption } from '@/src/types/common';
import { Translation } from '@/src/types/translation';

interface Props {
values: DropdownSelectorOption[];
options: DropdownSelectorOption[];
placeholder: string;
onChange: (options: readonly DropdownSelectorOption[]) => void;
}

export function DropdownSelector({
options,
placeholder,
onChange,
values,
}: Props) {
const { t } = useTranslation(Translation.Common);

return (
<Select
placeholder={placeholder}
isMulti
onChange={onChange}
closeMenuOnSelect={false}
name="colors"
options={options}
value={values}
components={{
ClearIndicator: (props) => (
<button type="button" className="group p-2">
<IconX
className="shrink-0 text-secondary group-hover:text-accent-primary"
onClick={() => props.clearValue()}
size={18}
/>
</button>
),
MultiValueRemove: (props) => (
<components.MultiValueRemove
{...props}
innerProps={{
...props.innerProps,
style: {
...props.innerProps.style,
backgroundColor: 'transparent',
},
className: 'group',
}}
>
<IconX
className="cursor-pointer text-secondary group-hover:text-accent-primary"
size={16}
/>
</components.MultiValueRemove>
),
}}
styles={{
indicatorsContainer: (styles) => ({ ...styles, cursor: 'default' }),
input: (styles) => ({
...styles,
height: '21px',
padding: 0,
margin: 0,
color: 'var(--text-primary)',
}),
menu: (styles) => ({ ...styles, margin: 0 }),
menuList: (styles) => ({
...styles,
margin: 0,
padding: 0,
backgroundColor: 'var(--bg-layer-0)',
}),
option: (styles, state) => ({
...styles,
WebkitTapHighlightColor: state.data.backgroundColor,
backgroundColor: '',
cursor: 'pointer',
':hover': {
backgroundColor: state.data.backgroundColor,
},
}),
dropdownIndicator: (styles, state) => ({
...styles,
transition: 'all',
transitionDuration: '200ms',
color: 'var(--text-primary)',
cursor: 'pointer',
transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : '',
':hover': {
color: 'var(--text-primary)',
},
}),
indicatorSeparator: (styles, state) => ({
...styles,
visibility: state.hasValue ? 'visible' : 'hidden',
backgroundColor: 'var(--text-secondary)',
}),
multiValue: (styles, state) => ({
...styles,
margin: '0 4px 0 0',
height: '28px',
backgroundColor: state.data.backgroundColor,
borderWidth: '1px',
borderColor: state.data.borderColor,
padding: '0 8px',
}),
multiValueLabel: (styles) => ({
...styles,
height: '100%',
display: 'flex',
alignItems: 'center',
padding: '0 4px',
color: 'var(--text-primary)',
}),
valueContainer: (styles) => ({
...styles,
padding: '4px',
}),
placeholder: (styles) => ({
...styles,
color: 'var(--text-secondary)',
margin: 0,
}),
noOptionsMessage: (styles) => ({
...styles,
textAlign: 'start',
}),
control: (styles, state) => ({
...styles,
paddingLeft: state.hasValue ? 0 : '8px',
display: 'flex',
cursor: 'text',
backgroundColor: 'var(--bg-layer-3)',
border: '1px solid var(--stroke-primary)',
boxShadow: 'none',
transition: 'all 0',
':hover': {
border: '1px solid var(--stroke-accent-primary)',
},
}),
}}
noOptionsMessage={() => t('No options')}
/>
);
}
Loading