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 @@ -47,6 +47,7 @@ const CreationFlow: React.FC<CreationFlowProps> = () => {
createNewBot,
saveProjectAs,
fetchProjectById,
createNewBotV2,
} = useRecoilValue(dispatcherState);

const templateProjects = useRecoilValue(filteredTemplatesSelector);
Expand Down Expand Up @@ -127,7 +128,11 @@ const CreationFlow: React.FC<CreationFlowProps> = () => {
alias: formData.alias,
preserveRoot: formData.preserveRoot,
};
createNewBot(newBotData);
if (templateId === 'conversationalcore') {
createNewBotV2(newBotData);
} else {
createNewBot(newBotData);
}
};

const handleSaveAs = (formData) => {
Expand Down
6 changes: 6 additions & 0 deletions Composer/packages/client/src/recoilModel/atoms/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { atom, atomFamily } from 'recoil';
import { FormDialogSchemaTemplate, FeatureFlagMap, BotTemplate, UserSettings } from '@bfc/shared';
import { ExtensionMetadata } from '@bfc/extension-client';
import formatMessage from 'format-message';

import {
StorageFolder,
Expand Down Expand Up @@ -212,6 +213,11 @@ export const botOpeningState = atom<boolean>({
default: false,
});

export const botOpeningMessage = atom({
key: getFullyQualifiedKey('botOpeningMessage'),
default: formatMessage('Loading'),
});

export const formDialogLibraryTemplatesState = atom<FormDialogSchemaTemplate[]>({
key: getFullyQualifiedKey('formDialogLibraryTemplates'),
default: [],
Expand Down
107 changes: 102 additions & 5 deletions Composer/packages/client/src/recoilModel/dispatchers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { BotProjectFile } from '@bfc/shared';
import formatMessage from 'format-message';
import findIndex from 'lodash/findIndex';
import { CallbackInterface, useRecoilCallback } from 'recoil';
Expand All @@ -17,7 +18,9 @@ import qnaFileStatusStorage from '../../utils/qnaFileStatusStorage';
import {
botErrorState,
botNameIdentifierState,
botOpeningMessage,
botOpeningState,
botProjectFileState,
botProjectIdsState,
botProjectSpaceLoadedState,
botStatusState,
Expand All @@ -32,6 +35,7 @@ import { logMessage, setError } from './../dispatchers/shared';
import {
checkIfBotExistsInBotProjectFile,
createNewBotFromTemplate,
createNewBotFromTemplateV2,
fetchProjectDataById,
flushExistingTasks,
getSkillNameIdentifier,
Expand Down Expand Up @@ -144,7 +148,7 @@ export const projectDispatcher = () => {
const { set, snapshot } = callbackHelpers;
const dispatcher = await snapshot.getPromise(dispatcherState);
try {
const { templateId, name, description, location, schemaUrl, locale, qnaKbUrls } = newProjectData;
const { templateId, name, description, location, schemaUrl, locale } = newProjectData;
set(botOpeningState, true);

const { projectId, mainDialog } = await createNewBotFromTemplate(
Expand All @@ -164,7 +168,7 @@ export const projectDispatcher = () => {
});
set(botProjectIdsState, (current) => [...current, projectId]);
await dispatcher.addLocalSkillToBotProjectFile(projectId);
navigateToBot(callbackHelpers, projectId, mainDialog, qnaKbUrls, templateId);
navigateToBot(callbackHelpers, projectId, mainDialog);
return projectId;
} catch (ex) {
handleProjectFailure(callbackHelpers, ex);
Expand Down Expand Up @@ -236,7 +240,6 @@ export const projectDispatcher = () => {
location,
schemaUrl,
locale,
qnaKbUrls,
templateDir,
eTag,
urlSuffix,
Expand Down Expand Up @@ -264,7 +267,7 @@ export const projectDispatcher = () => {
isRemote: false,
});
projectIdCache.set(projectId);
navigateToBot(callbackHelpers, projectId, mainDialog, qnaKbUrls, templateId, urlSuffix);
navigateToBot(callbackHelpers, projectId, mainDialog, urlSuffix);
} catch (ex) {
set(botProjectIdsState, []);
handleProjectFailure(callbackHelpers, ex);
Expand All @@ -274,6 +277,49 @@ export const projectDispatcher = () => {
}
});

const createNewBotV2 = useRecoilCallback((callbackHelpers: CallbackInterface) => async (newProjectData: any) => {
const { set, snapshot } = callbackHelpers;
try {
await flushExistingTasks(callbackHelpers);
const dispatcher = await snapshot.getPromise(dispatcherState);
set(botOpeningState, true);
const {
templateId,
name,
description,
location,
schemaUrl,
locale,
templateDir,
eTag,
urlSuffix,
alias,
preserveRoot,
} = newProjectData;
// starts the creation process and stores the jobID in state for tracking
const response = await createNewBotFromTemplateV2(
callbackHelpers,
templateId,
name,
description,
location,
schemaUrl,
locale,
templateDir,
eTag,
alias,
preserveRoot
);
if (response.data.jobId) {
dispatcher.updateCreationMessage(response.data.jobId, templateId, urlSuffix);
}
} catch (ex) {
set(botProjectIdsState, []);
handleProjectFailure(callbackHelpers, ex);
navigateTo('/home');
}
});

const saveProjectAs = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (oldProjectId, name, description, location) => {
const { set } = callbackHelpers;
Expand Down Expand Up @@ -365,7 +411,7 @@ export const projectDispatcher = () => {

const reloadProject = async (callbackHelpers: CallbackInterface, response: any) => {
callbackHelpers.reset(filePersistenceState(response.data.id));
const { projectData, botFiles } = loadProjectData(response);
const { projectData, botFiles } = loadProjectData(response.data);

await initBotState(callbackHelpers, projectData, botFiles);
};
Expand All @@ -377,9 +423,59 @@ export const projectDispatcher = () => {
await initBotState(callbackHelpers, projectData, botFiles);
});

const updateCreationMessage = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (jobId: string, templateId: string, urlSuffix: string) => {
const timer = setInterval(async () => {
try {
const response = await httpClient.get(`/status/${jobId}`);
if (response.data?.httpStatusCode === 200 && response.data.result) {
// Bot creation successful
clearInterval(timer);
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 currentBotProjectFileIndexed: BotProjectFile = botFiles.botProjectSpaceFiles[0];
callbackHelpers.set(botProjectFileState(projectId), currentBotProjectFileIndexed);

const mainDialog = await initBotState(callbackHelpers, projectData, botFiles);
callbackHelpers.set(botProjectIdsState, [projectId]);

// Post project creation
callbackHelpers.set(projectMetaDataState(projectId), {
isRootBot: true,
isRemote: false,
});
projectIdCache.set(projectId);
navigateToBot(callbackHelpers, projectId, mainDialog, urlSuffix);
callbackHelpers.set(botOpeningMessage, '');
callbackHelpers.set(botOpeningState, false);
} else {
if (response.data.httpStatusCode !== 500) {
// pending
callbackHelpers.set(botOpeningMessage, response.data.latestMessage);
} else {
// failure
callbackHelpers.set(botOpeningMessage, response.data.latestMessage);
clearInterval(timer);
}
}
} catch (err) {
clearInterval(timer);
callbackHelpers.set(botProjectIdsState, []);
handleProjectFailure(callbackHelpers, err);
navigateTo('/home');
}
}, 5000);
}
);

return {
openProject,
createNewBot,
createNewBotV2,
deleteBot,
saveProjectAs,
fetchProjectById,
Expand All @@ -395,5 +491,6 @@ export const projectDispatcher = () => {
replaceSkillInBotProject,
reloadProject,
reloadExistingProject,
updateCreationMessage,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ export const navigateToBot = (
callbackHelpers: CallbackInterface,
projectId: string,
mainDialog: string,
qnaKbUrls?: string[],
templateId?: string,
urlSuffix?: string
) => {
if (projectId) {
Expand All @@ -161,8 +159,8 @@ export const navigateToBot = (
}
};

export const loadProjectData = (response) => {
const { files, botName, settings, skills: skillContent, id: projectId } = response.data;
export const loadProjectData = (data) => {
const { files, botName, settings, skills: skillContent, id: projectId } = data;
const mergedSettings = getMergedSettings(projectId, settings);
const storedLocale = languageStorage.get(botName)?.locale;
const locale = settings.languages.includes(storedLocale) ? storedLocale : settings.defaultLanguage;
Expand All @@ -174,7 +172,7 @@ export const loadProjectData = (response) => {

return {
botFiles: { ...indexedFiles, qnaFiles: updateQnAFiles, mergedSettings },
projectData: response.data,
projectData: data,
error: undefined,
};
};
Expand All @@ -185,7 +183,7 @@ export const fetchProjectDataByPath = async (
): Promise<{ botFiles: any; projectData: any; error: any }> => {
try {
const response = await httpClient.put(`/projects/open`, { path, storageId });
const projectData = loadProjectData(response);
const projectData = loadProjectData(response.data);
return projectData;
} catch (ex) {
return {
Expand All @@ -199,7 +197,7 @@ export const fetchProjectDataByPath = async (
export const fetchProjectDataById = async (projectId): Promise<{ botFiles: any; projectData: any; error: any }> => {
try {
const response = await httpClient.get(`/projects/${projectId}`);
const projectData = loadProjectData(response);
const projectData = loadProjectData(response.data);
return projectData;
} catch (ex) {
return {
Expand Down Expand Up @@ -434,7 +432,7 @@ export const createNewBotFromTemplate = async (
alias,
preserveRoot,
});
const { botFiles, projectData } = loadProjectData(response);
const { botFiles, projectData } = loadProjectData(response.data);
const projectId = response.data.id;
if (settingStorage.get(projectId)) {
settingStorage.remove(projectId);
Expand All @@ -451,6 +449,35 @@ export const createNewBotFromTemplate = async (
return { projectId, mainDialog };
};

export const createNewBotFromTemplateV2 = async (
callbackHelpers,
templateId: string,
name: string,
description: string,
location: string,
schemaUrl?: string,
locale?: string,
templateDir?: string,
eTag?: string,
alias?: string,
preserveRoot?: boolean
) => {
const jobId = await httpClient.post(`/v2/projects`, {
storageId: 'default',
templateId,
name,
description,
location,
schemaUrl,
locale,
templateDir,
eTag,
alias,
preserveRoot,
});
return jobId;
};

const addProjectToBotProjectSpace = (set, projectId: string, skillCt: number) => {
let isBotProjectLoaded = false;
set(botProjectIdsState, (current: string[]) => {
Expand Down Expand Up @@ -559,7 +586,7 @@ export const saveProject = async (callbackHelpers, oldProjectData) => {
description,
location,
});
const data = loadProjectData(response);
const data = loadProjectData(response.data);
if (data.error) {
throw data.error;
}
Expand Down
12 changes: 10 additions & 2 deletions Composer/packages/client/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { resolveToBasePath } from './utils/fileUtil';
import { data } from './styles';
import { NotFound } from './components/NotFound';
import { BASEPATH } from './constants';
import { dispatcherState, schemasState, botProjectIdsState, botOpeningState, pluginPagesSelector } from './recoilModel';
import {
dispatcherState,
schemasState,
botProjectIdsState,
botOpeningState,
pluginPagesSelector,
botOpeningMessage,
} from './recoilModel';
import { openAlertModal } from './components/Modal/AlertDialog';
import { dialogStyle } from './components/Modal/dialogStyle';
import { LoadingSpinner } from './components/LoadingSpinner';
Expand All @@ -32,6 +39,7 @@ const FormDialogPage = React.lazy(() => import('./pages/form-dialog/FormDialogPa
const Routes = (props) => {
const botOpening = useRecoilValue(botOpeningState);
const pluginPages = useRecoilValue(pluginPagesSelector);
const spinnerText = useRecoilValue(botOpeningMessage);

return (
<div css={data}>
Expand Down Expand Up @@ -87,7 +95,7 @@ const Routes = (props) => {
<div
css={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, background: 'rgba(255, 255, 255, 0.6)' }}
>
<LoadingSpinner />
<LoadingSpinner message={spinnerText} />
</div>
)}
</div>
Expand Down
Loading