diff --git a/Composer/packages/client/src/pages/botProject/BotProjectSettingsTableView.tsx b/Composer/packages/client/src/pages/botProject/BotProjectSettingsTableView.tsx index 8f702bad8c..d5ef3baa3f 100644 --- a/Composer/packages/client/src/pages/botProject/BotProjectSettingsTableView.tsx +++ b/Composer/packages/client/src/pages/botProject/BotProjectSettingsTableView.tsx @@ -18,6 +18,7 @@ import { RuntimeSettings } from './RuntimeSettings'; import { PublishTargets } from './PublishTargets'; import { DeleteBotButton } from './DeleteBotButton'; import AdapterSection from './adapters/AdapterSection'; +import { SkillConfiguration } from './skill-configuration'; // -------------------- Styles -------------------- // @@ -55,6 +56,7 @@ export const BotProjectSettingsTableView: React.FC + {isRootBot && } ); diff --git a/Composer/packages/client/src/pages/botProject/PublishTargets.tsx b/Composer/packages/client/src/pages/botProject/PublishTargets.tsx index 6695a15edb..5594cffd9b 100644 --- a/Composer/packages/client/src/pages/botProject/PublishTargets.tsx +++ b/Composer/packages/client/src/pages/botProject/PublishTargets.tsx @@ -18,7 +18,7 @@ import { AuthDialog } from '../../components/Auth/AuthDialog'; import { isShowAuthDialog } from '../../utils/auth'; import { PublishProfileDialog } from './create-publish-profile/PublishProfileDialog'; -import { title, tableRow, tableRowItem, tableColumnHeader, columnSizes } from './styles'; +import { title, tableRow, tableRowItem, tableColumnHeader, columnSizes, addNewButton } from './styles'; // -------------------- Styles -------------------- // @@ -33,16 +33,6 @@ const publishTargetsHeader = css` height: 42px; `; -const addPublishProfile = { - root: { - fontSize: 12, - fontWeight: FontWeights.regular, - color: SharedColors.cyanBlue10, - paddingLeft: 0, - marginLeft: 5, - }, -}; - const editPublishProfile = { root: { fontSize: 12, @@ -149,7 +139,7 @@ export const PublishTargets: React.FC = (props) => { })} { if (isShowAuthDialog(true)) { setShowAuthDialog(true); diff --git a/Composer/packages/client/src/pages/botProject/skill-configuration/AllowedCallers.tsx b/Composer/packages/client/src/pages/botProject/skill-configuration/AllowedCallers.tsx new file mode 100644 index 0000000000..774dbacdbd --- /dev/null +++ b/Composer/packages/client/src/pages/botProject/skill-configuration/AllowedCallers.tsx @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { jsx } from '@emotion/core'; +import React from 'react'; +import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; +import { ActionButton, IconButton } from 'office-ui-fabric-react/lib/components/Button'; +import { FluentTheme } from '@uifabric/fluent-theme'; +import { Stack } from 'office-ui-fabric-react/lib/components/Stack'; +import { ITextField, TextField } from 'office-ui-fabric-react/lib/components/TextField'; +import cloneDeep from 'lodash/cloneDeep'; +import formatMessage from 'format-message'; + +import { dispatcherState, rootBotProjectIdSelector, settingsState } from '../../../recoilModel'; +import { mergePropertiesManagedByRootBot } from '../../../recoilModel/dispatchers/utils/project'; +import { addNewButton, tableColumnHeader } from '../styles'; + +const Input = styled(TextField)({ + width: '100%', + position: 'relative', + '& .ms-TextField-fieldGroup:focus::after': { + content: '""', + position: 'absolute', + left: -1, + top: -1, + right: -1, + bottom: -1, + pointerEvents: 'none', + borderRadius: 2, + border: `2px solid ${FluentTheme.palette.themePrimary}`, + zIndex: 1, + }, +}); + +const textFieldStyles = { + fieldGroup: { + borderColor: 'transparent', + transition: 'border-color 0.1s linear', + selectors: { + ':hover': { + borderColor: FluentTheme.palette.neutralLight, + }, + }, + }, +}; + +const ItemContainer = styled.div({ + borderTop: `1px solid ${FluentTheme.palette.neutralLight}`, + marginTop: '4px', +}); + +const Row = styled(Stack)({ + borderBottom: `1px solid ${FluentTheme.palette.neutralLight}`, + padding: '8px 0 8px 4px', + '& .ms-Button:not(:focus) i': { + visibility: 'hidden', + }, + '&:hover .ms-Button i': { + visibility: 'visible', + }, +}); + +type ItemProps = { + value: string; + onBlur: () => void; + onChange: (event: React.FormEvent, newValue?: string) => void; + onRemove: () => void; +}; + +const Item = React.memo(({ value, onBlur, onChange, onRemove }: ItemProps) => { + const itemRef = React.useRef(null); + const didMount = React.useRef(false); + + React.useEffect(() => { + if (!value && !didMount.current) { + itemRef.current?.focus(); + } + didMount.current = true; + }, []); + + return ( + + (itemRef.current = ref)} + styles={textFieldStyles} + value={value} + onBlur={onBlur} + onChange={onChange} + /> + + + ); +}); + +type Props = { + projectId: string; +}; + +export const AllowedCallers: React.FC = ({ projectId }) => { + const { setSettings } = useRecoilValue(dispatcherState); + const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector) || ''; + const settings = useRecoilValue(settingsState(projectId)); + const mergedSettings = mergePropertiesManagedByRootBot(projectId, rootBotProjectId, settings); + const { skillConfiguration } = mergedSettings; + + const updateAllowedCallers = React.useCallback( + (allowedCallers: string[] = []) => { + const updatedSetting = { + ...cloneDeep(mergedSettings), + skillConfiguration: { ...skillConfiguration, allowedCallers }, + }; + setSettings(projectId, updatedSetting); + }, + [mergedSettings, projectId, skillConfiguration] + ); + + const onBlur = React.useCallback(() => { + updateAllowedCallers(skillConfiguration?.allowedCallers?.filter(Boolean)); + }, [skillConfiguration?.allowedCallers, updateAllowedCallers]); + + const onChange = React.useCallback( + (index: number) => (_: React.FormEvent, newValue = '') => { + const updatedAllowedCallers = [...(skillConfiguration?.allowedCallers || [])]; + updatedAllowedCallers[index] = newValue; + updateAllowedCallers(updatedAllowedCallers); + }, + [skillConfiguration?.allowedCallers, updateAllowedCallers] + ); + + const onRemove = React.useCallback( + (index: number) => () => { + const updatedAllowedCallers = skillConfiguration?.allowedCallers?.filter((_, itemIndex) => itemIndex !== index); + updateAllowedCallers(updatedAllowedCallers); + }, + [skillConfiguration?.allowedCallers, updateAllowedCallers] + ); + + const onAddNewAllowedCaller = React.useCallback(() => { + updateAllowedCallers([...skillConfiguration?.allowedCallers, '']); + }, [skillConfiguration?.allowedCallers, updateAllowedCallers]); + + return ( + +
{formatMessage('Allowed callers')}
+ + {skillConfiguration?.allowedCallers?.map((caller, index) => { + return ( + + ); + })} + + + {formatMessage('Add new')} + +
+ ); +}; diff --git a/Composer/packages/client/src/pages/botProject/skill-configuration/SkillToggle.tsx b/Composer/packages/client/src/pages/botProject/skill-configuration/SkillToggle.tsx new file mode 100644 index 0000000000..93407c649d --- /dev/null +++ b/Composer/packages/client/src/pages/botProject/skill-configuration/SkillToggle.tsx @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { css, jsx } from '@emotion/core'; +import React from 'react'; +import { useRecoilValue } from 'recoil'; +import { Toggle } from 'office-ui-fabric-react/lib/components/Toggle'; +import cloneDeep from 'lodash/cloneDeep'; +import formatMessage from 'format-message'; + +import { dispatcherState, rootBotProjectIdSelector, settingsState } from '../../../recoilModel'; +import { mergePropertiesManagedByRootBot } from '../../../recoilModel/dispatchers/utils/project'; + +const toggle = css` + display: flex; + flex-direction: column; + box-sizing: border-box; +`; + +type Props = { + projectId: string; +}; + +export const SkillToggle: React.FC = ({ projectId }) => { + const { setSettings } = useRecoilValue(dispatcherState); + const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector) || ''; + const settings = useRecoilValue(settingsState(projectId)); + const mergedSettings = mergePropertiesManagedByRootBot(projectId, rootBotProjectId, settings); + const { skillConfiguration } = mergedSettings; + + const updateIsSkill = React.useCallback( + (isSkill: boolean) => { + const updatedSetting = { + ...cloneDeep(mergedSettings), + skillConfiguration: { ...skillConfiguration, isSkill }, + }; + setSettings(projectId, updatedSetting); + }, + [mergedSettings, projectId, skillConfiguration] + ); + + const toggleIsSKill = React.useCallback( + (event, checked?: boolean) => { + updateIsSkill(!!checked); + }, + [updateIsSkill] + ); + + return ( +
+ +
+ ); +}; diff --git a/Composer/packages/client/src/pages/botProject/skill-configuration/index.tsx b/Composer/packages/client/src/pages/botProject/skill-configuration/index.tsx new file mode 100644 index 0000000000..fdd7c49dfe --- /dev/null +++ b/Composer/packages/client/src/pages/botProject/skill-configuration/index.tsx @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React from 'react'; +import formatMessage from 'format-message'; + +import { CollapsableWrapper } from '../../../components/CollapsableWrapper'; +import { title } from '../styles'; + +import { AllowedCallers } from './AllowedCallers'; +import { SkillToggle } from './SkillToggle'; + +type Props = { + projectId: string; +}; + +export const SkillConfiguration: React.FC = ({ projectId }) => { + return ( + + + + + ); +}; diff --git a/Composer/packages/client/src/pages/botProject/styles.ts b/Composer/packages/client/src/pages/botProject/styles.ts index bdeb155cd7..696c4e28f2 100644 --- a/Composer/packages/client/src/pages/botProject/styles.ts +++ b/Composer/packages/client/src/pages/botProject/styles.ts @@ -121,3 +121,13 @@ export const unknownIconStyle = (required: boolean) => { }; export const columnSizes = ['300px', '150px', '150px']; + +export const addNewButton = { + root: { + fontSize: 12, + fontWeight: FontWeights.regular, + color: SharedColors.cyanBlue10, + paddingLeft: 0, + marginLeft: 5, + }, +}; diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index 07a82bbfa2..fd6cb7a0cf 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -230,6 +230,12 @@ "all_4321c3a1": { "message": "All" }, + "allow_bot_to_be_called_as_skill_d3496fd8": { + "message": "Allow bot to be called as skill" + }, + "allowed_callers_fe0f5bfe": { + "message": "Allowed callers" + }, "an_authoring_key_is_created_automatically_when_you_21cf77aa": { "message": "An authoring key is created automatically when you create a LUIS account." }, @@ -2780,6 +2786,9 @@ "remove_f47dc62a": { "message": "Remove" }, + "remove_item_5877e701": { + "message": "Remove item" + }, "remove_this_dialog_6146716c": { "message": "Remove this dialog" }, @@ -3059,6 +3068,9 @@ "skill_9b084d2e": { "message": "Skill" }, + "skill_configuration_5e4bfbcd": { + "message": "Skill configuration" + }, "skill_dialog_name_1bbf0eff": { "message": "Skill Dialog Name" },