diff --git a/src/core/i18n/en/translation.en.json b/src/core/i18n/en/translation.en.json
index 7718c51c43..273f7cc3bc 100644
--- a/src/core/i18n/en/translation.en.json
+++ b/src/core/i18n/en/translation.en.json
@@ -3168,6 +3168,15 @@
"initialPosts": "Initial Posts",
"delete": "Delete post"
},
+ "documents": {
+ "title": "Or add documents to your Knowledge Subspace.",
+ "tooltip": "Supported file formats: Text on websites, .pdf, .doc(x), .xls(x). Maximum file size: 20mb",
+ "referenceTitle": "Reference Title",
+ "referenceUrl": "Url",
+ "addAnother": "Add another document",
+ "remove": "Remove document",
+ "initialDocuments": "Initial Links and Documents"
+ },
"submitDisabled": "Add at least one Post to continue"
},
"externalAI": {
diff --git a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent.tsx b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent.tsx
deleted file mode 100644
index dd16c5ea52..0000000000
--- a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-import React, { useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import { Formik } from 'formik';
-import * as yup from 'yup';
-import { LoadingButton } from '@mui/lab';
-import { Box, Button, DialogContent, IconButton, Tooltip } from '@mui/material';
-import AddIcon from '@mui/icons-material/Add';
-import DialogHeader from '@/core/ui/dialog/DialogHeader';
-import { Caption } from '@/core/ui/typography';
-import Gutters from '@/core/ui/grid/Gutters';
-import FormikInputField from '@/core/ui/forms/FormikInputField/FormikInputField';
-import FormikMarkdownField from '@/core/ui/forms/MarkdownInput/FormikMarkdownField';
-import { Actions } from '@/core/ui/actions/Actions';
-import { LONG_MARKDOWN_TEXT_LENGTH, SMALL_TEXT_LENGTH } from '@/core/ui/forms/field-length.constants';
-import CancelDialog from './CancelDialog';
-import MarkdownValidator from '@/core/ui/forms/MarkdownInput/MarkdownValidator';
-import { pullAt } from 'lodash';
-import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
-import { gutters } from '@/core/ui/grid/utils';
-import { MessageWithPayload } from '@/domain/shared/i18n/ValidationMessageTranslation';
-
-type AddContentProps = {
- onClose: () => void;
- onCreateVC: (posts: PostsFormValues) => Promise;
-};
-
-export interface PostValues {
- title: string;
- description: string;
-}
-
-export interface PostsFormValues {
- posts: PostValues[];
-}
-
-const MAX__POSTS = 25;
-const MIN__POSTS = 1;
-
-const AddContent = ({ onClose, onCreateVC }: AddContentProps) => {
- const { t } = useTranslation();
- const [dialogOpen, setDialogOpen] = useState(false);
-
- const validationSchema = yup.object().shape({
- posts: yup
- .array()
- .of(
- yup.object().shape({
- title: yup
- .string()
- .min(3, MessageWithPayload('forms.validations.minLength'))
- .max(SMALL_TEXT_LENGTH, MessageWithPayload('forms.validations.maxLength'))
- .required(MessageWithPayload('forms.validations.requiredField')),
- description: MarkdownValidator(LONG_MARKDOWN_TEXT_LENGTH),
- })
- )
- .min(MIN__POSTS, MessageWithPayload('forms.validations.minLength')),
- });
-
- const initialValues: PostsFormValues = {
- posts: [
- {
- title: t('createVirtualContributorWizard.addContent.post.exampleTitle'),
- description: t('createVirtualContributorWizard.addContent.post.exampleDescription'),
- },
- ],
- };
-
- const newPost = () => ({
- title: '',
- description: '',
- });
-
- const onCancel = () => {
- setDialogOpen(true);
- };
-
- return (
- <>
- {t('createVirtualContributorWizard.addContent.title')}
-
-
- {t('createVirtualContributorWizard.addContent.description')}
- {t('createVirtualContributorWizard.addContent.descriptionBold')}
-
- {({ values: { posts }, isValid, setFieldValue }) => {
- const handleAdd = () => {
- const newArray = [...posts, newPost()];
- setFieldValue('posts', newArray);
- };
- const handleDelete = (index: number) => {
- const nextPosts = [...posts];
- pullAt(nextPosts, index);
- setFieldValue('posts', nextPosts);
- };
- return (
- <>
- {posts.map((post, index) => (
-
-
-
-
- {posts.length > MIN__POSTS && (
-
- handleDelete(index)}
- size="large"
- aria-label={t('createVirtualContributorWizard.addContent.post.delete')}
- sx={{ marginTop: gutters(-1), alignSelf: 'flex-end' }}
- >
-
-
-
- )}
-
-
- ))}
-
- = MAX__POSTS
- ? t('createVirtualContributorWizard.addContent.post.tooltip')
- : t('createVirtualContributorWizard.addContent.post.addAnotherPost')
- }
- placement={'bottom-start'}
- >
-
- }
- disabled={posts.length >= MAX__POSTS}
- onClick={() => {
- handleAdd();
- }}
- >
- {t('createVirtualContributorWizard.addContent.post.addAnotherPost')}
-
-
-
-
-
-
-
-
-
- onCreateVC({ posts })}>
- {t('buttons.continue')}
-
-
-
-
-
- >
- );
- }}
-
-
-
- setDialogOpen(false)} onConfirm={onClose} />
- >
- );
-};
-
-export default AddContent;
diff --git a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContent.tsx b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContent.tsx
new file mode 100644
index 0000000000..cc64ff645f
--- /dev/null
+++ b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContent.tsx
@@ -0,0 +1,36 @@
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { DialogContent } from '@mui/material';
+import DialogHeader from '@/core/ui/dialog/DialogHeader';
+import Gutters from '@/core/ui/grid/Gutters';
+import { Caption } from '@/core/ui/typography';
+import { gutters } from '@/core/ui/grid/utils';
+import { AddContentForm } from './AddContentForm';
+import { AddContentProps } from './AddContentProps';
+import CancelDialog from '../CancelDialog';
+import { StorageConfigContextProvider } from '@/domain/storage/StorageBucket/StorageConfigContext';
+
+const AddContent = ({ onClose, onCreateVC, spaceId }: AddContentProps) => {
+ const { t } = useTranslation();
+ const [dialogOpen, setDialogOpen] = useState(false);
+
+ const onCancel = () => {
+ setDialogOpen(true);
+ };
+
+ return (
+
+ {t('createVirtualContributorWizard.addContent.title')}
+
+
+ {t('createVirtualContributorWizard.addContent.description')}
+ {t('createVirtualContributorWizard.addContent.descriptionBold')}
+
+
+
+ setDialogOpen(false)} onConfirm={onClose} />
+
+ );
+};
+
+export default AddContent;
diff --git a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContentForm.tsx b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContentForm.tsx
new file mode 100644
index 0000000000..6e5bf8e08a
--- /dev/null
+++ b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContentForm.tsx
@@ -0,0 +1,190 @@
+import { Formik } from 'formik';
+import { Trans, useTranslation } from 'react-i18next';
+import { pullAt } from 'lodash';
+import * as yup from 'yup';
+import { Box, Button, Tooltip } from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+import { gutters } from '@/core/ui/grid/utils';
+import { LoadingButton } from '@mui/lab';
+import { LONG_MARKDOWN_TEXT_LENGTH, SMALL_TEXT_LENGTH } from '@/core/ui/forms/field-length.constants';
+import { Actions } from '@/core/ui/actions/Actions';
+import { MessageWithPayload } from '@/domain/shared/i18n/ValidationMessageTranslation';
+import MarkdownValidator from '@/core/ui/forms/MarkdownInput/MarkdownValidator';
+import { BoKCalloutsFormValues } from './AddContentProps';
+import { PostItem } from './PostItem';
+import { Caption } from '@/core/ui/typography';
+import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
+import { DocumentItem } from '@/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/DocumentItem';
+
+const MAX_POSTS = 25;
+
+const newPost = () => ({
+ title: '',
+ description: '',
+});
+
+const newDocument = () => ({
+ name: '',
+ url: '',
+});
+
+export const AddContentForm = ({
+ onSubmit,
+ onCancel,
+}: {
+ onCancel: () => void;
+ onSubmit: (values: BoKCalloutsFormValues) => void;
+}) => {
+ const { t } = useTranslation();
+
+ const validationSchema = yup.object().shape({
+ posts: yup
+ .array()
+ .of(
+ yup.object().shape({
+ title: yup
+ .string()
+ .min(3, MessageWithPayload('forms.validations.minLength'))
+ .max(SMALL_TEXT_LENGTH, MessageWithPayload('forms.validations.maxLength'))
+ .required(MessageWithPayload('forms.validations.requiredField')),
+ description: MarkdownValidator(LONG_MARKDOWN_TEXT_LENGTH),
+ })
+ )
+ .min(1, MessageWithPayload('forms.validations.minLength')),
+ });
+
+ const initialValues: BoKCalloutsFormValues = {
+ posts: [
+ {
+ title: t('createVirtualContributorWizard.addContent.post.exampleTitle'),
+ description: t('createVirtualContributorWizard.addContent.post.exampleDescription'),
+ },
+ ],
+ documents: [
+ {
+ name: '',
+ url: '',
+ },
+ ],
+ };
+
+ return (
+
+ {({ values: { posts, documents }, isValid, setFieldValue }) => {
+ const moreThanOnePost = posts.length > 1;
+ const maxPostsReached = posts.length >= MAX_POSTS;
+
+ const handleAddPost = () => {
+ const newArray = [...posts, newPost()];
+ setFieldValue('posts', newArray);
+ };
+ const handleDeletePost = (index: number) => {
+ const nextPosts = [...posts];
+ pullAt(nextPosts, index);
+ setFieldValue('posts', nextPosts);
+ };
+ const handleAddDocument = () => {
+ const newArrayDocuments = [...documents, newDocument()];
+ setFieldValue('documents', newArrayDocuments);
+ };
+ const handleDeleteDocument = (index: number) => {
+ const nextDocuments = [...documents];
+ pullAt(nextDocuments, index);
+ setFieldValue('documents', nextDocuments);
+ };
+
+ return (
+ <>
+ {posts.map((post, index) => (
+
+ ))}
+
+
+
+ }
+ disabled={maxPostsReached}
+ onClick={() => handleAddPost()}
+ >
+ {t('createVirtualContributorWizard.addContent.post.addAnotherPost')}
+
+
+
+
+
+ ,
+ tooltip: (
+
+ <>>
+
+ ),
+ }}
+ />
+
+ {documents?.map((document, index) => (
+
+ ))}
+
+
+ } onClick={handleAddDocument}>
+ {t('createVirtualContributorWizard.addContent.documents.addAnother')}
+
+
+
+
+
+
+
+
+
+ onSubmit({ posts, documents })}
+ >
+ {t('buttons.continue')}
+
+
+
+
+
+ >
+ );
+ }}
+
+ );
+};
diff --git a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContentProps.ts b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContentProps.ts
new file mode 100644
index 0000000000..88c742f8ea
--- /dev/null
+++ b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/AddContentProps.ts
@@ -0,0 +1,20 @@
+export type PostValues = {
+ title: string;
+ description: string;
+};
+
+export interface DocumentValues {
+ name: string;
+ url: string;
+}
+
+export interface BoKCalloutsFormValues {
+ posts: PostValues[];
+ documents: DocumentValues[];
+}
+
+export type AddContentProps = {
+ onClose: () => void;
+ onCreateVC: (values: BoKCalloutsFormValues) => Promise;
+ spaceId: string;
+};
diff --git a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/DocumentItem.tsx b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/DocumentItem.tsx
new file mode 100644
index 0000000000..f2ce59a502
--- /dev/null
+++ b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/DocumentItem.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { Box, Tooltip, IconButton, Theme, useMediaQuery, Link } from '@mui/material';
+import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
+import FormikInputField from '@/core/ui/forms/FormikInputField/FormikInputField';
+import Gutters from '@/core/ui/grid/Gutters';
+import { DocumentValues } from './AddContentProps';
+import FormikFileInput from '@/core/ui/forms/FormikFileInput/FormikFileInput';
+import { useConfig } from '@/domain/platform/config/useConfig';
+import { TranslateWithElements } from '@/domain/shared/i18n/TranslateWithElements';
+
+interface DocumentItemProps {
+ document: DocumentValues;
+ index: number;
+ onDelete: (index: number) => void;
+}
+
+export const DocumentItem = ({ document, index, onDelete }: DocumentItemProps) => {
+ const { t } = useTranslation();
+ const { locations } = useConfig();
+ const tLinks = TranslateWithElements();
+
+ const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm'));
+
+ return (
+
+
+
+
+
+
+
+ onDelete(index)} size="large" color="primary">
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/PostItem.tsx b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/PostItem.tsx
new file mode 100644
index 0000000000..ba5521b85e
--- /dev/null
+++ b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/AddContent/PostItem.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { Box, Tooltip, IconButton } from '@mui/material';
+import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
+import { LONG_MARKDOWN_TEXT_LENGTH } from '@/core/ui/forms/field-length.constants';
+import FormikInputField from '@/core/ui/forms/FormikInputField/FormikInputField';
+import FormikMarkdownField from '@/core/ui/forms/MarkdownInput/FormikMarkdownField';
+import Gutters from '@/core/ui/grid/Gutters';
+import { gutters } from '@/core/ui/grid/utils';
+import { PostValues } from './AddContentProps';
+
+interface PostItemProps {
+ post: PostValues;
+ index: number;
+ onDelete: (index: number) => void;
+ hasDelete: boolean;
+}
+
+export const PostItem = ({ post, index, onDelete, hasDelete }: PostItemProps) => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+ {hasDelete && (
+
+ onDelete(index)}
+ size="large"
+ aria-label={t('createVirtualContributorWizard.addContent.post.delete')}
+ sx={{ marginTop: gutters(-1), alignSelf: 'flex-end' }}
+ >
+
+
+
+ )}
+
+
+ );
+};
diff --git a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/useNewVirtualContributorWizard.tsx b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/useNewVirtualContributorWizard.tsx
index 81cd78d06a..8b391afa6f 100644
--- a/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/useNewVirtualContributorWizard.tsx
+++ b/src/main/topLevelPages/myDashboard/newVirtualContributorWizard/useNewVirtualContributorWizard.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
refetchSubspacesInSpaceQuery,
useCreateSpaceMutation,
@@ -13,6 +13,7 @@ import {
useSubspaceCommunityAndRoleSetIdLazyQuery,
useAssignRoleToVirtualContributorMutation,
refetchDashboardWithMembershipsQuery,
+ useCreateLinkOnCalloutMutation,
} from '@/core/apollo/generated/apollo-hooks';
import {
AiPersonaBodyOfKnowledgeType,
@@ -28,7 +29,8 @@ import {
} from '@/core/apollo/generated/graphql-schema';
import CreateNewVirtualContributor, { VirtualContributorFromProps } from './CreateNewVirtualContributor';
import LoadingState from './LoadingState';
-import AddContent, { PostsFormValues, PostValues } from './AddContent';
+import AddContent from './AddContent/AddContent';
+import { BoKCalloutsFormValues, DocumentValues, PostValues } from './AddContent/AddContentProps';
import ExistingSpace, { SelectableKnowledgeProps } from './ExistingSpace';
import { useTranslation } from 'react-i18next';
import { useNotification } from '@/core/ui/notifications/useNotification';
@@ -88,8 +90,9 @@ const useNewVirtualContributorWizard = (): useNewVirtualContributorWizardProvide
const [boKParentRoleSetId, setBoKParentRoleSetId] = useState();
const [creationIndex, setCreationIndex] = useState(0); // used in case of space deletion
const [virtualContributorInput, setVirtualContributorInput] = useState();
- const [calloutPostData, setCalloutPostData] = useState();
+ const [calloutData, setCalloutData] = useState();
const [tryCreateCallout, setTryCreateCallout] = useState(false);
+ const calloutId = useRef();
const startWizard = (initAccount: UserAccountProps | undefined) => {
setTargetAccount(initAccount);
@@ -102,8 +105,8 @@ const useNewVirtualContributorWizard = (): useNewVirtualContributorWizardProvide
setStep(step);
};
- const handleAddContent = async (posts: PostsFormValues) => {
- setCalloutPostData(posts);
+ const handleAddContent = async (values: BoKCalloutsFormValues) => {
+ setCalloutData(values);
// trigger useEffect to create the callout once canCreateCallout is true
setTryCreateCallout(true);
};
@@ -386,6 +389,23 @@ const useNewVirtualContributorWizard = (): useNewVirtualContributorWizardProvide
sendNotification: false,
};
+ const calloutDocumentsDetails: CalloutCreationType = {
+ framing: {
+ profile: {
+ displayName: t('createVirtualContributorWizard.addContent.documents.initialDocuments'),
+ description: '',
+ referencesData: [],
+ },
+ },
+ type: CalloutType.LinkCollection,
+ contributionPolicy: {
+ state: CalloutState.Open,
+ },
+ groupName: CalloutGroupName.Home,
+ visibility: CalloutVisibility.Published,
+ sendNotification: false,
+ };
+
// no need of 'update' implementation as the callout is on another page
// where CalloutDetails will refetch the details
const [createPost] = useCreatePostFromContributeTabMutation();
@@ -405,23 +425,58 @@ const useNewVirtualContributorWizard = (): useNewVirtualContributorWizardProvide
});
};
+ const [createLinkOnCallout] = useCreateLinkOnCalloutMutation();
+ const onCreateLink = async (document: DocumentValues, calloutId: string) => {
+ await createLinkOnCallout({
+ variables: {
+ input: {
+ calloutID: calloutId,
+ link: {
+ uri: document.url,
+ profile: {
+ displayName: document.name,
+ },
+ },
+ },
+ },
+ });
+ };
+
const createCalloutContent = async () => {
setStep('loadingVCSetup');
// create collection of posts
- if (calloutPostData?.posts && calloutPostData?.posts.length > 0 && bokId) {
- const callout = await handleCreateCallout(calloutDetails);
+ if (calloutData?.posts && calloutData?.posts.length > 0 && bokId) {
+ const postCallout = await handleCreateCallout(calloutDetails);
+ const postCalloutId = postCallout?.id;
+ calloutId.current = postCalloutId;
// add posts to collection
- if (callout?.id) {
- for (const post of calloutPostData?.posts) {
- await onCreatePost(post, callout.id);
+ if (postCalloutId) {
+ const postsArray = calloutData?.posts ?? [];
+ for (const post of postsArray) {
+ await onCreatePost(post, postCalloutId);
}
}
+ }
- setTryCreateCallout(false);
+ // create collection of docs & links
+ if (calloutData?.documents && calloutData?.documents.length > 0 && bokId) {
+ const documentsCallout = await handleCreateCallout(calloutDocumentsDetails);
+ const documentsCalloutId = documentsCallout?.id;
+ calloutId.current = documentsCalloutId;
+
+ // add documents to collection
+ if (documentsCalloutId) {
+ const documentsArray = calloutData?.documents ?? [];
+ for (const document of documentsArray) {
+ await onCreateLink(document, documentsCalloutId);
+ }
+ }
}
+ setTryCreateCallout(false);
+
// create VC
if (virtualContributorInput && myAccountId && bokId && bokRoleSetId) {
const creationSuccess = await handleCreateVirtualContributor({
@@ -606,7 +661,11 @@ const useNewVirtualContributorWizard = (): useNewVirtualContributorWizardProvide
)}
{step === 'addKnowledge' && virtualContributorInput && (
-
+
)}
{step === 'existingKnowledge' && (