Skip to content

Commit

Permalink
feat: Orchestrator Recognizer Preview (#4867)
Browse files Browse the repository at this point in the history
* bf-orchestrator lib integration

* add orchestrator uischema

* Update Orchestrator Libs (minor interface changes)

* Add Orchestrator Recognizer to AzureWebApp

* Keep native libs out of asar package because of relative linking

* Local scenario POC

* Update orchestrator-core for electron

* write downsampling result to the interruption folder

* Update orchestrator-core critical bits for mac build

* Move download of model back to "start bot"

* First pass putting orchestrator behind feature flag

* filter the publish and separate the lu files

* start bot error fix

* Minor cleanup

* Fix linter error

* Remove obsolete test code

* Change writefile to async  variation in fs-extra

* Add safeguard to prevent endless loop

* Move FeatureFlag types, address typing issues

* Interface tweaks

* update the type and dispatcher

* remove the luprovider:''

* remove invalid import

* Minor tweak to single import

* Strengthen the check to prevent UI infinite loop

* Minor UI fix after feature flag is deselected

Co-authored-by: leilzh <leilzh@microsoft.com>
Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com>
  • Loading branch information
3 people authored Nov 20, 2020
1 parent 9ec8735 commit c874aa0
Show file tree
Hide file tree
Showing 51 changed files with 889 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export const RecognizerField: React.FC<FieldProps<MicrosoftIRecognizer>> = (prop

useMigrationEffect(value, onChange);
const { recognizers: recognizerConfigs, currentRecognizer } = useRecognizerConfig();
const dropdownOptions = useMemo(() => getDropdownOptions(recognizerConfigs), [recognizerConfigs]);
const dropdownOptions = useMemo(() => getDropdownOptions(recognizerConfigs, shellData, shellApi), [
recognizerConfigs,
]);

const RecognizerEditor = currentRecognizer?.recognizerEditor;
const widget = RecognizerEditor ? <RecognizerEditor {...props} /> : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { RecognizerSchema, FallbackRecognizerKey } from '@bfc/extension-client';
import { RecognizerSchema, FallbackRecognizerKey, ShellApi, ShellData } from '@bfc/extension-client';

import { recognizerOrderMap } from './defaultRecognizerOrder';
import { mapRecognizerSchemaToDropdownOption } from './mappers';

const getRankScore = (r: RecognizerSchema) => {
const getRankScore = (r: RecognizerSchema, shellData: ShellData, shellApi: ShellApi) => {
// Always put disabled recognizer behind. Handle 'disabled' before 'default'.
if (r.disabled) return Number.MAX_VALUE;
if ((typeof r.disabled === 'function' && r.disabled(shellData, shellApi)) || r.disabled) return Number.MAX_VALUE;
// Always put default recognzier ahead.
if (r.default) return -1;
// Put fallback recognizer behind.
if (r.id === FallbackRecognizerKey) return Number.MAX_VALUE - 1;
return recognizerOrderMap[r.id] ?? Number.MAX_VALUE - 1;
};

export const getDropdownOptions = (recognizerConfigs: RecognizerSchema[]) => {
export const getDropdownOptions = (recognizerConfigs: RecognizerSchema[], shellData: ShellData, shellApi: ShellApi) => {
return recognizerConfigs
.filter((r) => !r.disabled)
.filter((r) => (typeof r.disabled === 'function' && !r.disabled(shellData, shellApi)) || !r.disabled)
.sort((r1, r2) => {
return getRankScore(r1) - getRankScore(r2);
return getRankScore(r1, shellData, shellApi) - getRankScore(r2, shellData, shellApi);
})
.map(mapRecognizerSchemaToDropdownOption);
};
1 change: 1 addition & 0 deletions Composer/packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@bfc/ui-plugin-dialog-schema-editor": "*",
"@bfc/ui-plugin-lg": "*",
"@bfc/ui-plugin-luis": "*",
"@bfc/ui-plugin-orchestrator": "*",
"@bfc/ui-plugin-prompts": "*",
"@bfc/ui-plugin-select-dialog": "*",
"@bfc/ui-plugin-select-skill-dialog": "*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { FeatureFlagKey } from '@bfc/shared';
import { FeatureFlagKey } from '@botframework-composer/types';
import React, { Fragment } from 'react';

import { useFeatureFlag } from '../utils/hooks';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { navigateTo, openInEmulator } from '../../utils/navigation';
import plugins from '../../plugins';
import { useShell } from '../../shell/useShell';

import { isBuildConfigComplete, needsBuild } from './../../utils/buildUtil';
import { isBuildConfigComplete, isKeyRequired, needsBuild } from './../../utils/buildUtil';
import { PublishDialog } from './publishDialog';
import { ErrorCallout } from './errorCallout';
import { EmulatorOpenButton } from './emulatorOpenButton';
Expand Down Expand Up @@ -181,25 +181,27 @@ export const TestControllerContent: React.FC<{ projectId: string }> = (props) =>

async function handleStart() {
dismissCallout();
const config = Object.assign(
{},
{
luis: settings.luis,
qna: settings.qna,
}
);
if (!isAbsHosted() && needsBuild(dialogs)) {
if (
botStatus === BotStatus.failed ||
botStatus === BotStatus.pending ||
!isBuildConfigComplete(config, dialogs, luFiles, qnaFiles)
) {
openDialog();
} else {
await handleBuild(config);
}
const config = {
luis: { ...settings.luis },
qna: { ...settings.qna },
};

if (isAbsHosted() || !needsBuild(dialogs)) {
return await handleLoadBot();
}

if (!isKeyRequired(dialogs, luFiles, qnaFiles)) {
return await handleBuild(config);
}

if (
botStatus === BotStatus.failed ||
botStatus === BotStatus.pending ||
!isBuildConfigComplete(config, dialogs, luFiles, qnaFiles)
) {
openDialog();
} else {
await handleLoadBot();
await handleBuild(config);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { Dropdown, ResponsiveMode, IDropdownOption } from 'office-ui-fabric-reac
import { Text, Tips, Links, nameRegex } from '../../constants';
import { FieldConfig, useForm } from '../../hooks/useForm';
import { getReferredQnaFiles } from '../../utils/qnaUtil';
import { getReferredLuFiles } from '../../utils/luUtil';
import { dialogsSelectorFamily, luFilesState, qnaFilesState } from '../../recoilModel';
import { getLuisBuildLuFiles } from '../../utils/luUtil';
import { luFilesState, qnaFilesState, validateDialogsSelectorFamily } from '../../recoilModel';

// -------------------- Styles -------------------- //
const textFieldLabel = css`
Expand Down Expand Up @@ -105,11 +105,11 @@ interface IPublishDialogProps {

export const PublishDialog: React.FC<IPublishDialogProps> = (props) => {
const { isOpen, onDismiss, onPublish, botName, config, projectId } = props;
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const dialogs = useRecoilValue(validateDialogsSelectorFamily(projectId));
const luFiles = useRecoilValue(luFilesState(projectId));
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
const qnaConfigShow = getReferredQnaFiles(qnaFiles, dialogs).length > 0;
const luConfigShow = getReferredLuFiles(luFiles, dialogs).length > 0;
const luConfigShow = getLuisBuildLuFiles(luFiles, dialogs).length > 0;

const formConfig: FieldConfig<FormData> = {
name: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { jsx } from '@emotion/core';
import React from 'react';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { FeatureFlagKey } from '@bfc/shared';
import { FeatureFlagKey } from '@botframework-composer/types';

import * as styles from './styles';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { jsx } from '@emotion/core';
import { Fragment, useState } from 'react';
import formatMessage from 'format-message';
import { FeatureFlag, FeatureFlagKey } from '@bfc/shared';
import { FeatureFlag, FeatureFlagKey } from '@botframework-composer/types';
import { useRecoilValue } from 'recoil';

import { dispatcherState, featureFlagsState } from '../../../recoilModel';
Expand Down
2 changes: 2 additions & 0 deletions Composer/packages/client/src/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import prompts from '@bfc/ui-plugin-prompts';
import schemaEditor from '@bfc/ui-plugin-dialog-schema-editor';
import selectDialog from '@bfc/ui-plugin-select-dialog';
import selectSkillDialog from '@bfc/ui-plugin-select-skill-dialog';
import orchestrator from '@bfc/ui-plugin-orchestrator';
import lg from '@bfc/ui-plugin-lg';
import lu from '@bfc/ui-plugin-luis';
import crossTrained from '@bfc/ui-plugin-cross-trained';
Expand Down Expand Up @@ -43,6 +44,7 @@ export default mergePluginConfigs(
selectSkillDialog,
lg,
lu,
orchestrator,
crossTrained,
schemaEditor
);
25 changes: 17 additions & 8 deletions Composer/packages/client/src/recoilModel/Recognizers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { crossTrainConfigState, luFilesState, qnaFilesState, settingsState } fro
import { dialogsSelectorFamily } from './selectors';
import { recognizersSelectorFamily } from './selectors/recognizers';

const LuisRecognizerTemplate = (target: string, fileName: string) => ({
export const LuisRecognizerTemplate = (target: string, fileName: string) => ({
$kind: SDKKinds.LuisRecognizer,
id: `LUIS_${target}`,
applicationId: `=settings.luis.${fileName.replace(/[.-]/g, '_')}_lu.appId`,
Expand All @@ -24,28 +24,34 @@ const LuisRecognizerTemplate = (target: string, fileName: string) => ({
endpointKey: '=settings.luis.endpointKey',
});

const QnAMakerRecognizerTemplate = (target: string, fileName: string) => ({
export const QnAMakerRecognizerTemplate = (target: string, fileName: string) => ({
$kind: SDKKinds.QnAMakerRecognizer,
id: `QnA_${target}`,
knowledgeBaseId: `=settings.qna.${fileName.replace(/[.-]/g, '_')}_qna`,
hostname: '=settings.qna.hostname',
endpointKey: '=settings.qna.endpointKey',
});

const MultiLanguageRecognizerTemplate = (target: string, fileType: 'lu' | 'qna') => ({
export const MultiLanguageRecognizerTemplate = (target: string, fileType: 'lu' | 'qna') => ({
$kind: SDKKinds.MultiLanguageRecognizer,
id: `${fileType === 'lu' ? 'LUIS' : 'QnA'}_${target}`,
recognizers: {},
});

const CrossTrainedRecognizerTemplate = (): {
$kind: string;
export const CrossTrainedRecognizerTemplate = (): {
$kind: SDKKinds.CrossTrainedRecognizerSet;
recognizers: string[];
} => ({
$kind: SDKKinds.CrossTrainedRecognizerSet,
recognizers: [],
});

export const OrchestratorRecognizerTemplate = (target: string, fileName: string) => ({
$kind: SDKKinds.OrchestratorRecognizer,
modelPath: '=settings.orchestrator.modelPath',
snapshotPath: `=settings.orchestrator.snapshots.${fileName.replace(/[.-]/g, '_')}`,
});

export const getMultiLanguagueRecognizerDialog = (
target: string,
files: { empty: boolean; id: string }[],
Expand Down Expand Up @@ -140,6 +146,7 @@ export const Recognizer = React.memo((props: { projectId: string }) => {
const luFiles = useRecoilValue(luFilesState(projectId));
const qnaFiles = useRecoilValue(qnaFilesState(projectId));
const settings = useRecoilValue(settingsState(projectId));
const curRecognizers = useRecoilValue(recognizersSelectorFamily(projectId));

useEffect(() => {
let recognizers: RecognizerFile[] = [];
Expand All @@ -160,17 +167,19 @@ export const Recognizer = React.memo((props: { projectId: string }) => {

if (luisRecognizers.length) {
recognizers.push(luMultiLanguagueRecognizer);
recognizers = [...recognizers, ...preserveRecognizer(luisRecognizers, [])];
recognizers = [...recognizers, ...preserveRecognizer(luisRecognizers, curRecognizers)];
}
if (isCrossTrain) {
recognizers.push(crossTrainedRecognizer);
}
if (isCrossTrain && qnaMakeRecognizers.length) {
recognizers.push(qnaMultiLanguagueRecognizer);
recognizers = [...recognizers, ...preserveRecognizer(qnaMakeRecognizers, [])];
recognizers = [...recognizers, ...preserveRecognizer(qnaMakeRecognizers, curRecognizers)];
}
});
setRecognizers(recognizers);
if (!isEqual([...recognizers].sort(), [...curRecognizers].sort())) {
setRecognizers(recognizers);
}
}, [dialogs, luFiles, qnaFiles]);

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion Composer/packages/client/src/recoilModel/atoms/botState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const recognizerIdsState = atomFamily<string[], string>({
export const recognizerState = atomFamily<RecognizerFile, { projectId: string; id: string }>({
key: getFullyQualifiedKey('recognizer'),
default: () => {
return { id: '', content: {}, lastModified: '' };
return {} as RecognizerFile;
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/* eslint-disable react-hooks/rules-of-hooks */
import { SDKKinds } from '@bfc/shared';
import { LuProviderType } from '@botframework-composer/types';
import { CallbackInterface, useRecoilCallback } from 'recoil';

import { recognizerState } from '../atoms';
import { LuisRecognizerTemplate, OrchestratorRecognizerTemplate } from '../Recognizers';

import { recognizersSelectorFamily } from './../selectors/recognizers';

const LuProviderRecognizer = [SDKKinds.OrchestratorRecognizer, SDKKinds.LuisRecognizer];

const templates = {
[SDKKinds.OrchestratorRecognizer]: OrchestratorRecognizerTemplate,
[SDKKinds.LuisRecognizer]: LuisRecognizerTemplate,
};

export const recognizerDispatcher = () => {
const updateRecognizer = useRecoilCallback(
({ set }: CallbackInterface) => async (projectId: string, recognizerId: string, content: any) => {
set(recognizerState({ projectId, id: recognizerId }), content);
({ set, snapshot }: CallbackInterface) => async (projectId: string, dialogId: string, kind: LuProviderType) => {
const recognizers = await snapshot.getPromise(recognizersSelectorFamily(projectId));

const updates = recognizers.filter(
({ id, content }) =>
id.split('.')[0] === dialogId && LuProviderRecognizer.includes(content.$kind) && content.$kind !== kind
);

updates.forEach(({ id }) => {
set(recognizerState({ projectId, id }), {
id,
content: templates[kind](dialogId, id.replace('.lu.dialog', '')),
});
});
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { useRecoilCallback, CallbackInterface } from 'recoil';
import isArray from 'lodash/isArray';
import formatMessage from 'format-message';
import { FeatureFlagKey, FeatureFlagMap } from '@bfc/shared';
import { FeatureFlagMap, FeatureFlagKey } from '@botframework-composer/types';

import httpClient from '../../utils/httpUtil';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { selectorFamily } from 'recoil';
import { validateDialog } from '@bfc/indexers';

import { botProjectIdsState, dialogIdsState, schemasState, lgFilesState, luFilesState, dialogState } from '../atoms';
import { getLuProvider } from '../../utils/dialogUtil';

import { recognizersSelectorFamily } from './recognizers';

type validateDialogSelectorFamilyParams = { projectId: string; dialogId: string };
const validateDialogSelectorFamily = selectorFamily({
Expand All @@ -14,8 +17,9 @@ const validateDialogSelectorFamily = selectorFamily({
const schemas = get(schemasState(projectId));
const lgFiles = get(lgFilesState(projectId));
const luFiles = get(luFilesState(projectId));

return { ...dialog, diagnostics: validateDialog(dialog, schemas.sdk.content, lgFiles, luFiles) };
const recognizers = get(recognizersSelectorFamily(projectId));
const luProvider = getLuProvider(dialogId, recognizers);
return { ...dialog, diagnostics: validateDialog(dialog, schemas.sdk.content, lgFiles, luFiles), luProvider };
},
});

Expand Down
8 changes: 6 additions & 2 deletions Composer/packages/client/src/shell/useShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

import { useMemo, useRef } from 'react';
import { ShellApi, ShellData, Shell, DialogSchemaFile, DialogInfo } from '@botframework-composer/types';
import { ShellApi, ShellData, Shell, DialogSchemaFile, DialogInfo, FeatureFlagKey } from '@botframework-composer/types';
import { useRecoilValue } from 'recoil';
import formatMessage from 'format-message';

Expand All @@ -27,6 +27,7 @@ import {
luFilesState,
rateInfoState,
rootBotProjectIdSelector,
featureFlagsState,
} from '../recoilModel';
import { undoFunctionState } from '../recoilModel/undo/history';
import httpClient from '../utils/httpUtil';
Expand Down Expand Up @@ -82,9 +83,9 @@ export function useShell(source: EventSource, projectId: string): Shell {
const settings = useRecoilValue(settingsState(projectId));
const flowZoomRate = useRecoilValue(rateInfoState);
const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector);

const userSettings = useRecoilValue(userSettingsState);
const clipboardActions = useRecoilValue(clipboardActionsState);
const featureFlags = useRecoilValue(featureFlagsState);
const {
updateDialog,
updateDialogSchema,
Expand All @@ -101,6 +102,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
displayManifestModal,
updateSkill,
updateZoomRate,
updateRecognizer,
reloadProject,
setApplicationLevelError,
} = useRecoilValue(dispatcherState);
Expand Down Expand Up @@ -202,6 +204,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
commitChanges();
});
},
updateRecognizer,
updateRegExIntent: updateRegExIntentHandler,
renameRegExIntent: renameRegExIntentHandler,
updateIntentTrigger: updateIntentTriggerHandler,
Expand Down Expand Up @@ -232,6 +235,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
redo,
commitChanges,
displayManifestModal: (skillId) => displayManifestModal(skillId, projectId),
isFeatureEnabled: (featureFlagKey: FeatureFlagKey): boolean => featureFlags?.[featureFlagKey]?.enabled ?? false,
updateDialogSchema: async (dialogSchema: DialogSchemaFile) => {
updateDialogSchema(dialogSchema, projectId);
},
Expand Down
Loading

0 comments on commit c874aa0

Please sign in to comment.