Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import axios from 'axios';
import querystring from 'query-string';

import msftIcon from '../../../images/msftIcon.svg';
import { DialogCreationCopy, EmptyBotTemplateId, feedDictionary } from '../../../constants';
import { fetchReadMePendingState, selectedTemplateReadMeState } from '../../../recoilModel';
import { DialogCreationCopy, feedDictionary } from '../../../constants';
import { creationFlowTypeState, fetchReadMePendingState, selectedTemplateReadMeState } from '../../../recoilModel';
import TelemetryClient from '../../../telemetry/TelemetryClient';
import { getAliasFromPayload } from '../../../utils/electronUtil';

Expand Down Expand Up @@ -136,6 +136,7 @@ export function CreateOptionsV2(props: CreateOptionsProps) {
const [selectedFeed, setSelectedFeed] = useState<{ props: IPivotItemProps }>({ props: { itemKey: csharpFeedKey } });
const [readMe] = useRecoilState(selectedTemplateReadMeState);
const fetchReadMePending = useRecoilValue(fetchReadMePendingState);
const creationFlowType = useRecoilValue(creationFlowTypeState);

const selectedTemplate = useMemo(() => {
return new Selection({
Expand Down Expand Up @@ -209,7 +210,7 @@ export function CreateOptionsV2(props: CreateOptionsProps) {

useEffect(() => {
if (templates.length > 1) {
const emptyBotTemplate = find(templates, ['id', EmptyBotTemplateId]);
const emptyBotTemplate = find(templates, ['id', defaultTemplateId]);
if (emptyBotTemplate) {
setCurrentTemplateId(emptyBotTemplate.id);
setEmptyBotKey(emptyBotTemplate.id);
Expand Down Expand Up @@ -254,14 +255,12 @@ export function CreateOptionsV2(props: CreateOptionsProps) {
}
}, [currentTemplateId, props.fetchReadMe]);

const dialogWrapperProps =
creationFlowType === 'Skill' ? DialogCreationCopy.CREATE_NEW_SKILLBOT : DialogCreationCopy.CREATE_NEW_BOT_V2;

return (
<Fragment>
<DialogWrapper
isOpen={isOpen}
{...DialogCreationCopy.CREATE_NEW_BOT_V2}
dialogType={DialogTypes.CreateFlow}
onDismiss={onDismiss}
>
<DialogWrapper isOpen={isOpen} {...dialogWrapperProps} dialogType={DialogTypes.CreateFlow} onDismiss={onDismiss}>
<Pivot
defaultSelectedKey={csharpFeedKey}
onLinkClick={(item) => {
Expand Down
86 changes: 72 additions & 14 deletions Composer/packages/client/src/pages/design/creationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Path from 'path';

import React, { Fragment, useEffect, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { BotTemplate } from '@botframework-composer/types';

import { CreateOptions } from '../../components/CreationFlow/CreateOptions';
import { OpenProject } from '../../components/CreationFlow/OpenProject';
Expand All @@ -16,9 +17,12 @@ import {
creationFlowTypeState,
userSettingsState,
templateProjectsState,
featureFlagsState,
} from '../../recoilModel';
import { CreationFlowStatus } from '../../constants';
import TelemetryClient from '../../telemetry/TelemetryClient';
import DefineConversationV2 from '../../components/CreationFlow/v2/DefineConversation';
import { CreateOptionsV2 } from '../../components/CreationFlow/v2/CreateOptions';

interface CreationModalProps {
onSubmit: () => void;
Expand All @@ -36,14 +40,18 @@ export const CreationModal: React.FC<CreationModalProps> = (props) => {
updateFolder,
saveTemplateId,
createNewBot,
createNewBotV2,
openProject,
addNewSkillToBotProject,
addExistingSkillToBotProject,
fetchTemplatesV2,
fetchReadMe,
} = useRecoilValue(dispatcherState);

const templateProjects = useRecoilValue(templateProjectsState);
const creationFlowStatus = useRecoilValue(creationFlowStatusState);
const creationFlowType = useRecoilValue(creationFlowTypeState);
const featureFlags = useRecoilValue(featureFlagsState);
const focusedStorageFolder = useRecoilValue(focusedStorageFolderState);
const { appLocale } = useRecoilValue(userSettingsState);
const storages = useRecoilValue(storagesState);
Expand Down Expand Up @@ -90,11 +98,34 @@ export const CreationModal: React.FC<CreationModalProps> = (props) => {
appLocale,
};
if (creationFlowType === 'Skill') {
addNewSkillToBotProject(newBotData);
TelemetryClient.track('AddNewSkillCompleted');
if (featureFlags?.NEW_CREATION_FLOW?.enabled) {
const templateVersion = templateProjects.find((template: BotTemplate) => {
return template.id == templateId;
})?.package?.packageVersion;
const newCreationBotData = {
templateId: templateId || '',
templateVersion: templateVersion || '',
name: formData.name,
description: formData.description,
location: formData.location,
schemaUrl: formData.schemaUrl,
appLocale,
templateDir: formData?.pvaData?.templateDir,
eTag: formData?.pvaData?.eTag,
urlSuffix: formData?.pvaData?.urlSuffix,
preserveRoot: formData?.pvaData?.preserveRoot,
alias: formData?.alias,
profile: formData?.profile,
source: formData?.source,
};
createNewBotV2(newCreationBotData);
} else {
addNewSkillToBotProject(newBotData);
}
} else {
createNewBot(newBotData);
}
TelemetryClient.track('AddNewSkillCompleted');
};

const handleDismiss = () => {
Expand All @@ -103,11 +134,6 @@ export const CreationModal: React.FC<CreationModalProps> = (props) => {
};

const handleDefineConversationSubmit = async (formData, templateId: string) => {
// If selected template is vaCore then route to VA Customization modal
if (templateId === 'va-core') {
return;
}

handleSubmit(formData, templateId);
};

Expand All @@ -132,9 +158,21 @@ export const CreationModal: React.FC<CreationModalProps> = (props) => {
}
};

return (
<Fragment>
{creationFlowStatus === CreationFlowStatus.NEW_FROM_TEMPLATE ? (
const renderDefineConversation = () => {
if (featureFlags?.NEW_CREATION_FLOW?.enabled) {
return (
<DefineConversationV2
createFolder={createFolder}
focusedStorageFolder={focusedStorageFolder}
templateId={templateId}
updateFolder={updateFolder}
onCurrentPathUpdate={updateCurrentPath}
onDismiss={handleDismiss}
onSubmit={handleDefineConversationSubmit}
/>
);
} else {
return (
<DefineConversation
createFolder={createFolder}
focusedStorageFolder={focusedStorageFolder}
Expand All @@ -144,11 +182,31 @@ export const CreationModal: React.FC<CreationModalProps> = (props) => {
onDismiss={handleDismiss}
onSubmit={handleDefineConversationSubmit}
/>
) : null}
);
}
};

{creationFlowStatus === CreationFlowStatus.NEW ? (
<CreateOptions templates={templateProjects} onDismiss={handleDismiss} onNext={handleCreateNext} />
) : null}
const renderCreateOptions = () => {
if (featureFlags?.NEW_CREATION_FLOW?.enabled) {
return (
<CreateOptionsV2
fetchReadMe={fetchReadMe}
fetchTemplates={fetchTemplatesV2}
templates={templateProjects}
onDismiss={handleDismiss}
onNext={handleCreateNext}
/>
);
} else {
return <CreateOptions templates={templateProjects} onDismiss={handleDismiss} onNext={handleCreateNext} />;
}
};

return (
<Fragment>
{creationFlowStatus === CreationFlowStatus.NEW_FROM_TEMPLATE ? renderDefineConversation() : null}

{creationFlowStatus === CreationFlowStatus.NEW ? renderCreateOptions() : null}

{creationFlowStatus === CreationFlowStatus.OPEN ? (
<OpenProject
Expand Down
65 changes: 36 additions & 29 deletions Composer/packages/client/src/recoilModel/dispatchers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
botProjectSpaceLoadedState,
botStatusState,
createQnAOnState,
creationFlowTypeState,
currentProjectIdState,
dispatcherState,
fetchReadMePendingState,
Expand All @@ -35,7 +36,7 @@ import {
showCreateQnAFromUrlDialogState,
} from '../atoms';
import { botRuntimeOperationsSelector, rootBotProjectIdSelector } from '../selectors';
import { mergePropertiesManagedByRootBot } from '../../recoilModel/dispatchers/utils/project';
import { mergePropertiesManagedByRootBot, postRootBotCreation } from '../../recoilModel/dispatchers/utils/project';

import { announcementState, boilerplateVersionState, recentProjectsState, templateIdState } from './../atoms';
import { logMessage, setError } from './../dispatchers/shared';
Expand All @@ -53,7 +54,6 @@ import {
navigateToSkillBot,
openLocalSkill,
openRemoteSkill,
openRootBotAndSkills,
openRootBotAndSkillsByPath,
openRootBotAndSkillsByProjectId,
removeRecentProject,
Expand Down Expand Up @@ -106,7 +106,11 @@ export const projectDispatcher = () => {
);

const addExistingSkillToBotProject = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (path: string, storageId = 'default'): Promise<void> => {
(callbackHelpers: CallbackInterface) => async (
path: string,
storageId = 'default',
templateId?: string
): Promise<void> => {
const { set, snapshot } = callbackHelpers;
try {
set(botOpeningState, true);
Expand All @@ -128,6 +132,11 @@ export const projectDispatcher = () => {
throw error;
}

if (templateId === QnABotTemplateId) {
callbackHelpers.set(createQnAOnState, { projectId, dialogId: mainDialog });
callbackHelpers.set(showCreateQnAFromUrlDialogState(projectId), true);
}

set(botProjectIdsState, (current) => [...current, projectId]);
await dispatcher.addLocalSkillToBotProjectFile(projectId);
navigateToSkillBot(rootBotProjectId, projectId, mainDialog);
Expand Down Expand Up @@ -338,7 +347,13 @@ export const projectDispatcher = () => {
const createNewBotV2 = useRecoilCallback((callbackHelpers: CallbackInterface) => async (newProjectData: any) => {
const { set, snapshot } = callbackHelpers;
try {
await flushExistingTasks(callbackHelpers);
const creationFlowType = await callbackHelpers.snapshot.getPromise(creationFlowTypeState);

// flush existing tasks for new root bot creation
if (creationFlowType != 'Skill') {
await flushExistingTasks(callbackHelpers);
}

const dispatcher = await snapshot.getPromise(dispatcherState);
set(botOpeningState, true);
const {
Expand Down Expand Up @@ -500,36 +515,28 @@ export const projectDispatcher = () => {
if (response.data?.httpStatusCode === 200 && response.data.result) {
// Bot creation successful
clearInterval(timer);
const creationFlowType = await callbackHelpers.snapshot.getPromise(creationFlowTypeState);

callbackHelpers.set(botOpeningMessage, response.data.latestMessage);
const { botFiles, projectData } = loadProjectData(response.data.result);
const projectId = response.data.result.id;
if (settingStorage.get(projectId)) {
settingStorage.remove(projectId);
}

const { mainDialog } = await openRootBotAndSkills(callbackHelpers, { botFiles, projectData });

// Post project creation
callbackHelpers.set(projectMetaDataState(projectId), {
isRootBot: true,
isRemote: false,
});
// if create from QnATemplate, continue creation flow.
if (templateId === QnABotTemplateId) {
callbackHelpers.set(createQnAOnState, { projectId, dialogId: mainDialog });
callbackHelpers.set(showCreateQnAFromUrlDialogState(projectId), true);
}
if (profile) {
// ABS Create Flow, update publishProfile after create project
const dispatcher = await callbackHelpers.snapshot.getPromise(dispatcherState);
const newProfile = getPublishProfileFromPayload(profile, source);

newProfile && dispatcher.setPublishTargets([newProfile], projectId);
if (creationFlowType === 'Skill') {
// Skill Creation
await addExistingSkillToBotProject(projectData.location, 'default', templateId);
} else {
// Root Bot Creation
await postRootBotCreation(
callbackHelpers,
projectId,
botFiles,
projectData,
templateId,
profile,
source,
projectIdCache
);
}
projectIdCache.set(projectId);

// navigate to the new get started section
navigateToBot(callbackHelpers, projectId, undefined, btoa('botProjectsSettings#getstarted'));
callbackHelpers.set(botOpeningMessage, '');
callbackHelpers.set(botOpeningState, false);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { logMessage, setError } from '../shared';
import { setRootBotSettingState } from '../setting';
import { lgFilesSelectorFamily } from '../../selectors/lg';
import { createMissingLgTemplatesForDialogs } from '../../../utils/lgUtil';
import { getPublishProfileFromPayload } from '../../../utils/electronUtil';

import { crossTrainConfigState } from './../../atoms/botState';
import { recognizersSelectorFamily } from './../../selectors/recognizers';
Expand Down Expand Up @@ -693,6 +694,44 @@ export const openRootBotAndSkills = async (callbackHelpers: CallbackInterface, d
};
};

export const postRootBotCreation = async (
callbackHelpers,
projectId,
botFiles,
projectData,
templateId,
profile,
source,
projectIdCache
) => {
callbackHelpers.set(botProjectIdsState, (current) => [...current, projectId]);

if (settingStorage.get(projectId)) {
settingStorage.remove(projectId);
}
const { mainDialog } = await openRootBotAndSkills(callbackHelpers, { botFiles, projectData });
callbackHelpers.set(projectMetaDataState(projectId), {
isRootBot: true,
isRemote: false,
});
// if create from QnATemplate, continue creation flow.
if (templateId === QnABotTemplateId) {
callbackHelpers.set(createQnAOnState, { projectId, dialogId: mainDialog });
callbackHelpers.set(showCreateQnAFromUrlDialogState(projectId), true);
}
if (profile) {
// ABS Create Flow, update publishProfile after create project
const dispatcher = await callbackHelpers.snapshot.getPromise(dispatcherState);
const newProfile = getPublishProfileFromPayload(profile, source);

newProfile && dispatcher.setPublishTargets([newProfile], projectId);
}
projectIdCache.set(projectId);

// navigate to the new get started section
navigateToBot(callbackHelpers, projectId, undefined, btoa('botProjectsSettings#getstarted'));
};

export const openRootBotAndSkillsByPath = async (callbackHelpers: CallbackInterface, path: string, storageId) => {
const data = await fetchProjectDataByPath(path, storageId);
if (data.error) {
Expand Down
4 changes: 2 additions & 2 deletions Composer/packages/server/src/controllers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ async function removeProject(req: Request, res: Response) {
async function openProject(req: Request, res: Response) {
if (!req.body.storageId || !req.body.path) {
res.status(400).json({
message: 'parameters not provided, require stoarge id and path',
message: 'parameters not provided, require storage id and path',
});
return;
}
Expand Down Expand Up @@ -195,7 +195,7 @@ async function openProject(req: Request, res: Response) {
async function saveProjectAs(req: Request, res: Response) {
if (!req.body.storageId || !req.body.name) {
res.status(400).json({
message: 'parameters not provided, require stoarge id and path',
message: 'parameters not provided, require storage id and path',
});
return;
}
Expand Down
Loading