in Composer/packages/client/src/components/AddRemoteSkillModal/CreateSkillModal.tsx [118:387]
manifestUrl: formatMessage(
'Endpoints should not be empty or endpoint should have endpoint url field in manifest json'
),
};
}
} else {
result.error = { manifestUrl: formatMessage('could not locate manifest.json in zip') };
}
} catch (err) {
// eslint-disable-next-line format-message/literal-pattern
result.error = { manifestUrl: formatMessage(err.toString()) };
}
return result;
};
const validateSKillName = (skillContent, setSkillManifest) => {
skillContent.name = skillContent.name.replace(skillNameRegex, '');
setSkillManifest(skillContent);
};
export const getSkillManifest = async (
projectId: string,
manifestUrl: string,
setSkillManifest,
setFormDataErrors,
setShowDetail
) => {
try {
const { data } = await httpClient.get(`/projects/${projectId}/skill/retrieveSkillManifest`, {
params: {
url: manifestUrl,
},
});
validateSKillName(data, setSkillManifest);
} catch (error) {
const httpMessage = error?.response?.data?.message;
const message = httpMessage?.match('Unexpected string in JSON')
? formatMessage('Error attempting to parse Skill manifest. There could be an error in its format.')
: formatMessage('Manifest URL can not be accessed');
setFormDataErrors({ ...error, manifestUrl: message });
setShowDetail(false);
}
};
const getTriggerFormData = (intent: string, content: string): TriggerFormData => ({
errors: {},
$kind: 'Microsoft.OnIntent',
event: '',
intent: intent,
triggerPhrases: content,
regEx: '',
});
const buttonStyle = { root: { marginLeft: '8px' } };
const setAppIdDialogStyles = {
dialog: {
title: {
fontWeight: FontWeights.bold,
fontSize: FontSizes.size20,
paddingTop: '14px',
paddingBottom: '11px',
},
subText: {
fontSize: FontSizes.size14,
marginBottom: '0px',
},
},
modal: {
main: {
maxWidth: '80% !important',
width: '960px !important',
},
},
};
export const CreateSkillModal: React.FC<CreateSkillModalProps> = (props) => {
const { projectId, addRemoteSkill, addTriggerToRoot, onDismiss } = props;
const [title, setTitle] = useState(addSkillDialog.SET_APP_ID);
const [showSetAppIdDialog, setShowSetAppIdDialog] = useState(true);
const [showIntentSelectDialog, setShowIntentSelectDialog] = useState(false);
const [formData, setFormData] = useState<{ manifestUrl: string; endpointName: string }>({
manifestUrl: '',
endpointName: '',
});
const [formDataErrors, setFormDataErrors] = useState<SkillFormDataErrors>({});
const [skillManifest, setSkillManifest] = useState<any | null>(null);
const [showDetail, setShowDetail] = useState(false);
const [createSkillDialogHidden, setCreateSkillDialogHidden] = useState(false);
const [manifestDirPath, setManifestDirPath] = useState('');
const [zipContent, setZipContent] = useState({});
const publishTypes = useRecoilValue(publishTypesState(projectId));
const { languages, luFeatures, runtime, publishTargets = [], MicrosoftAppId } = useRecoilValue(
settingsState(projectId)
);
const { dialogId } = useRecoilValue(designPageLocationState(projectId));
const rootDialog = useRecoilValue(rootDialogSelector(projectId));
const luFiles = useRecoilValue(luFilesSelectorFamily(projectId));
const { updateRecognizer, setMicrosoftAppProperties, setPublishTargets } = useRecoilValue(dispatcherState);
const { content: botProjectFile } = useRecoilValue(botProjectFileState(projectId));
const skillUrls = Object.keys(botProjectFile.skills).map((key) => botProjectFile.skills[key].manifest as string);
const debouncedValidateManifestURl = useRef(debounce(validateManifestUrl, 500)).current;
const validationHelpers = {
formDataErrors,
setFormDataErrors,
};
const options: IDropdownOption[] = useMemo(() => {
return skillManifest?.endpoints?.map((item) => {
return {
key: item.name,
// eslint-disable-next-line format-message/literal-pattern
text: formatMessage(item.name),
};
});
}, [skillManifest]);
const handleManifestUrlChange = (_, currentManifestUrl = '') => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { manifestUrl, ...rest } = formData;
debouncedValidateManifestURl(
{
formData: { manifestUrl: currentManifestUrl },
...validationHelpers,
},
skillUrls
);
setFormData({
...rest,
manifestUrl: currentManifestUrl,
});
setSkillManifest(null);
setShowDetail(false);
};
const validateUrl = useCallback(
(event) => {
event.preventDefault();
setShowDetail(true);
const localManifestPath = formData.manifestUrl.replace(/\\/g, '/');
getSkillManifest(projectId, formData.manifestUrl, setSkillManifest, setFormDataErrors, setShowDetail);
setManifestDirPath(localManifestPath.substring(0, localManifestPath.lastIndexOf('/')));
},
[projectId, formData]
);
const handleSubmit = async (event, content: string, enable: boolean) => {
event.preventDefault();
// add a remote skill, add skill identifier into botProj file
await addRemoteSkill(formData.manifestUrl, formData.endpointName, zipContent);
TelemetryClient.track('AddNewSkillCompleted', {
from: Object.keys(zipContent).length > 0 ? 'zip' : 'url',
});
// if added remote skill fail, just not addTrigger to root.
const skillId = location.href.match(/skill\/([^/]*)/)?.[1];
//if the root dialog is orchestrator recoginzer type or user chooses orchestrator type before connecting,
//add the trigger to the root dialog.
const boundId =
rootDialog && (rootDialog.luProvider === SDKKinds.OrchestratorRecognizer || enable) ? rootDialog.id : dialogId;
if (skillId) {
// add trigger with connect to skill action to root bot
const triggerFormData = getTriggerFormData(skillManifest.name, content);
await addTriggerToRoot(boundId, triggerFormData, skillId);
TelemetryClient.track('AddNewTriggerCompleted', { kind: 'Microsoft.OnIntent' });
}
if (enable) {
// update recognizor type to orchestrator
await updateRecognizer(projectId, boundId, SDKKinds.OrchestratorRecognizer);
}
};
const handleDismiss = () => {
setShowSetAppIdDialog(true);
onDismiss();
};
const handleGotoAddSkill = (publishTargetName: string) => {
const profileTarget = publishTargets.find((target) => target.name === publishTargetName);
const configuration = JSON.parse(profileTarget?.configuration || '');
setMicrosoftAppProperties(
projectId,
configuration.settings.MicrosoftAppId,
configuration.settings.MicrosoftAppPassword
);
setShowSetAppIdDialog(false);
setTitle({
subText: '',
title: addSkillDialog.SKILL_MANIFEST_FORM.title,
});
};
const handleGotoCreateProfile = () => {
setCreateSkillDialogHidden(true);
};
const handleBrowseButtonUpdate = async (path: string, files: Record<string, JSZipObject>) => {
// update path in input field
setFormData({
...formData,
manifestUrl: path,
});
const result = await validateLocalZip(files);
setFormDataErrors(result.error);
result.path && setManifestDirPath(result.path);
result.zipContent && setZipContent(result.zipContent);
if (result.manifestContent) {
validateSKillName(result.manifestContent, setSkillManifest);
setShowDetail(true);
}
};
useEffect(() => {
if (skillManifest?.endpoints) {
setFormData({
...formData,
endpointName: skillManifest.endpoints[0].name,
});
}
}, [skillManifest]);
useEffect(() => {
if (MicrosoftAppId) {
setShowSetAppIdDialog(false);
setTitle({
subText: '',
title: addSkillDialog.SKILL_MANIFEST_FORM.title,
});
}
}, [MicrosoftAppId]);
return (
<Fragment>
<DialogWrapper
dialogType={showSetAppIdDialog ? DialogTypes.Customer : DialogTypes.CreateFlow}
isOpen={!createSkillDialogHidden}
onDismiss={handleDismiss}
{...title}
customerStyle={setAppIdDialogStyles}
>
{showSetAppIdDialog && (
<Fragment>
<Separator styles={{ root: { marginBottom: '20px' } }} />
<SetAppId
projectId={projectId}
onDismiss={handleDismiss}
onGotoCreateProfile={handleGotoCreateProfile}
onNext={handleGotoAddSkill}
/>
</Fragment>
)}
{showIntentSelectDialog && (
<SelectIntent
dialogId={dialogId}
languages={languages}
luFeatures={luFeatures}
manifest={skillManifest}
manifestDirPath={manifestDirPath}
projectId={projectId}
rootLuFiles={luFiles}
runtime={runtime}
zipContent={zipContent}
onBack={() => {
setTitle({