Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
2 changes: 2 additions & 0 deletions Composer/packages/client/src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const ActionTypes = {
UPDATE_DIALOG: 'UPDATE_DIALOG',
UPDATE_DIALOG_FAILURE: 'UPDATE_DIALOG_FAILURE',
CREATE_DIALOG_SUCCESS: 'CREATE_DIALOG_SUCCESS',
UPDATE_LG_TEMPLATE: 'UPDATE_LG_TEMPLATE',
UPDATE_LG_FAILURE: 'UPDATE_LG_FAILURE',
BOT_STATUS_SET: 'BOT_STATUS_SET',
BOT_STATUS_SET_FAILURE: 'BOT_STATUS_SET_FAILURE',
EDITOR_ADD: 'EDITOR_ADD',
Expand Down
23 changes: 20 additions & 3 deletions Composer/packages/client/src/pages/content/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import React, { Fragment } from 'react';
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Fragment } from 'react';
import formatMessage from 'format-message';

import Routes from './router';
import { Tree } from './../../components/Tree/index';
import { Conversation } from './../../components/Conversation/index';
import { NavLink } from './../../components/NavLink/index';
import { title, label, navLinkClass } from './styles';

// todo: should wrap the NavLink to another component.
export const ContentPage = () => {
return (
<Fragment>
<div style={{ display: 'flex' }}>
<div style={{ flex: 1, marginLeft: '30px', marginTop: '20px' }}>
<Tree variant="largest">
<div>Content</div>
<div>
<div css={title}>{formatMessage('Content')}</div>
<NavLink to={'lu'} style={navLinkClass.default} activestyle={navLinkClass.activestyle}>
<div css={label}>{formatMessage('Language Understanding')}</div>
</NavLink>
<NavLink to={'lg'} style={navLinkClass.default} activestyle={navLinkClass.activestyle}>
<div css={label}>{formatMessage('Language Generation')}</div>
</NavLink>
</div>
</Tree>
</div>
<div style={{ flex: 4, marginTop: '20px', marginLeft: '20px' }}>
<Conversation />
<Conversation>
<Routes />
</Conversation>
</div>
</div>
</Fragment>
Expand Down
172 changes: 172 additions & 0 deletions Composer/packages/client/src/pages/content/lg-settings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/* eslint-disable react/display-name */
/** @jsx jsx */
import { jsx } from '@emotion/core';
import debounce from 'lodash.debounce';
import { Fragment, useContext, useRef } from 'react';
import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane';
import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky';
import { DetailsList, DetailsListLayoutMode, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import formatMessage from 'format-message';

import { Store } from '../../../store/index';

import { scrollablePaneRoot, title, label } from './styles';

export function LanguageGenerationSettings() {
const { state, actions } = useContext(Store);
const { lgTemplates } = state;
const updateLG = useRef(debounce(actions.updateLgTemplate, 500)).current;
const tableColums = [
{
key: 'name',
name: 'Name',
fieldName: 'name',
minWidth: 150,
maxWidth: 200,
isRowHeader: true,
isResizable: true,
data: 'string',
onRender: (item, index) => {
return (
<span>
<TextField
borderless
placeholder={formatMessage('Template Name.')}
defaultValue={item.name}
onChange={(event, newName) => updateTemplateContent(index, newName, item.content)}
/>
</span>
);
},
},
{
key: 'type',
name: 'Type',
fieldName: 'type',
minWidth: 50,
maxWidth: 100,
data: 'string',
isPadded: true,
onRender: item => {
return <span>{item.type}</span>;
},
},
{
key: 'phrase',
name: 'Sample phrase',
fieldName: 'samplePhrase',
minWidth: 500,
isResizable: true,
data: 'string',
isPadded: true,
onRender: (item, index) => {
return <span>{getTemplatePhrase(item, index)}</span>;
},
},
];

function updateTemplateContent(index, templateName, content) {
const newTemplate = lgTemplates[index];
newTemplate.name = templateName;
newTemplate.content = content;

const payload = {
name: templateName,
content: newTemplate,
};

updateLG(payload);
}

function getTemplatePhrase(item, index) {
return (
<TextField
borderless
multiline
autoAdjustHeight
placeholder={formatMessage('Template Content.')}
defaultValue={item.content}
onChange={(event, newValue) => updateTemplateContent(index, item.name, newValue)}
/>
);
}

function onRenderDetailsHeader(props, defaultRender) {
return (
<Sticky stickyPosition={StickyPositionType.Header} isScrollSynced={true}>
{defaultRender({
...props,
// eslint-disable-next-line react/display-name
onRenderColumnHeaderTooltip: tooltipHostProps => <TooltipHost {...tooltipHostProps} />,
})}
</Sticky>
);
}

const items = [];
if (lgTemplates) {
lgTemplates.forEach(template => {
items.push({
name: template.name,
value: template.name,
absolutePath: template.absolutePath,
type: template.type,
content: template.content,
comments: template.comments,
});
});
}

const groups = [];
let currentKey = '';
let itemCount = 0;
lgTemplates.forEach((template, index) => {
if (template.absolutePath !== currentKey) {
if (itemCount !== 0) {
const pathItems = currentKey.split(/[\\/]+/g);
groups.push({
name: pathItems[pathItems.length - 1],
count: itemCount,
key: currentKey,
startIndex: index - itemCount,
});
itemCount = 0;
}
currentKey = template.absolutePath;
}
itemCount++;
if (index === lgTemplates.length - 1) {
const pathItems = currentKey.split(/[\\/]+/g);
groups.push({
name: pathItems[pathItems.length - 1],
count: itemCount,
key: currentKey,
startIndex: index - itemCount + 1,
});
}
});

return (
<Fragment>
<div>
<div css={title}>{formatMessage('Content > Language Generation')}</div>
<div css={label}>{formatMessage('Templates')}</div>
<ScrollablePane css={scrollablePaneRoot} scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
items={items}
compact={false}
columns={tableColums}
setKey="key"
layoutMode={DetailsListLayoutMode.fixedColumns}
onRenderDetailsHeader={onRenderDetailsHeader}
selectionMode={SelectionMode.none}
groups={groups}
selectionPreservedOnEmptyClick={true}
/>
</ScrollablePane>
</div>
</Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { css } from '@emotion/core';

export const scrollablePaneRoot = css`
margin-top: 80px;
margin-left: 15px;
`;

export const title = css`
font-weight: bold;
color: #5f5f5f;
font-size: 20px;
line-height: 40px;
padding-left: 15px;
`;

export const label = css`
text-decoration: none;
color: rgb(95, 95, 95);
font-size: 13px;
font-weight: bold;
line-height: 30px;
padding-left: 15px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import formatMessage from 'format-message';

export function LanguageUnderstandingSettings() {
return <div>{formatMessage('Language Understanding')}</div>;
}
15 changes: 15 additions & 0 deletions Composer/packages/client/src/pages/content/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Router, Redirect } from '@reach/router';

import { LanguageUnderstandingSettings } from './lu-settings';
import { LanguageGenerationSettings } from './lg-settings';

const Routes = () => (
<Router>
<Redirect from="*" to="content/lg" noThrow />
<LanguageUnderstandingSettings path="lu" />
<LanguageGenerationSettings path="lg" />
</Router>
);

export default Routes;
28 changes: 28 additions & 0 deletions Composer/packages/client/src/pages/content/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { css } from '@emotion/core';

export const label = css`
font-size: 13px;
`;

export const title = css`
font-weight: bold;
color: #5f5f5f;
font-size: 20px;
line-height: 40px;
padding-left: 15px;
`;

export const navLinkClass = {
default: {
display: 'block',
textDecoration: 'none',
color: '#5f5f5f',
fontSize: '13px',
fontWeight: 'bold',
lineHeight: '30px',
paddingLeft: '15px',
},
activestyle: {
color: '#0083cb',
},
};
2 changes: 1 addition & 1 deletion Composer/packages/client/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { ContentPage } from './pages/content/index';
const Routes = () => (
<Router>
<DesignPage path="/" />
<ContentPage path="content" />
<SettingPage path="setting/*" />
<ContentPage path="content/*" />
<NotFound default />
</Router>
);
Expand Down
16 changes: 16 additions & 0 deletions Composer/packages/client/src/store/action/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,19 @@ export async function createDialog(dispatch, { name, steps }) {
console.error(err);
}
}

export async function updateLgTemplate(dispatch, { name, content }) {
try {
const response = await axios.put(`${BASEURL}/projects/opened/lgTemplates/${name}`, { name, content });
dispatch({
type: ActionTypes.UPDATE_LG_TEMPLATE,
payload: { response },
});
} catch (err) {
dispatch({
type: ActionTypes.UPDATE_LG_FAILURE,
payload: null,
error: err,
});
}
}
1 change: 1 addition & 0 deletions Composer/packages/client/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const initialState = {
focusedStorageFolder: {},
botStatus: 'stopped',
storageExplorerStatus: '',
lgTemplates: [],
};

export function StoreProvider(props) {
Expand Down
7 changes: 7 additions & 0 deletions Composer/packages/client/src/store/reducer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const closeCurrentProject = state => {
const getProjectSuccess = (state, { response }) => {
state.dialogs = response.data.dialogs;
state.botProjFile = response.data.botFile;
state.lgTemplates = response.data.lgTemplates;
return state;
};

Expand All @@ -25,6 +26,11 @@ const createDialogSuccess = (state, { response }) => {
return state;
};

const updateLgTemplate = (state, { response }) => {
state.lgTemplates = response.data.lgTemplates;
return state;
};

const updateProjFile = (state, { response }) => {
state.botProjFile = response.data.botFile;
return state;
Expand Down Expand Up @@ -116,4 +122,5 @@ export const reducer = createReducer({
[ActionTypes.NAVIGATE_DOWN]: navigateDown,
[ActionTypes.FOCUS_TO]: focusTo,
[ActionTypes.CLEAR_NAV_HISTORY]: clearNavHistory,
[ActionTypes.UPDATE_LG_TEMPLATE]: updateLgTemplate,
});
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,34 @@ describe('copyTo', () => {
expect(project.dialogs.length).toBe(3);
});
});

describe('update lg template', () => {
it('should update the lg template.', async () => {
const initFiles = [
{
name: 'test.lg',
content: '# greet\n- Hello!',
path: path.join(__dirname, '../../mocks/test.lg'),
relativePath: path.relative(proj.dir, path.join(__dirname, '../../mocks/test.lg')),
},
];
const initValue = {
id: 1,
name: 'greet',
content: '- Hello!',
absolutePath: path.join(__dirname, '../../mocks/test.lg'),
};
const newValue = {
id: 1,
name: 'updated',
content: '- new value',
absolutePath: path.join(__dirname, '../../mocks/test.lg'),
};
await proj.lgIndexer.index(initFiles);
const lgTemplates = await proj.updateLgTemplate('test', newValue);
const aTemplate = lgTemplates.find(f => f.name.startsWith('updated'));
// @ts-ignore
expect(aTemplate).toEqual(newValue);
await proj.updateLgTemplate('test', initValue);
});
});
Loading