Skip to content
This repository has been archived by the owner on Nov 16, 2023. It is now read-only.

Commit

Permalink
Add ability to select activity in existing log dialog and show relate…
Browse files Browse the repository at this point in the history
…d entity, memory, and action (#125)
  • Loading branch information
mattmazzola authored Sep 20, 2017
1 parent 0b4a5ba commit bc13b14
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 15 deletions.
21 changes: 18 additions & 3 deletions public/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,10 @@
.teachSessionWindow{
width: 100%
}
.teachVariationBox{
/* .teachVariationBox{
margin-top: 5px;
margin-left: 10px;
}
} */
.teachVariation{
overflow: hidden;
}
Expand Down Expand Up @@ -478,7 +478,7 @@
border-width: 1px;
border-color: #999999;
padding: 10px;
margin-bottom: 10px;
/* margin-bottom: 10px; */
}
.extractorResponseBoxInvalid{
border-style: solid;
Expand Down Expand Up @@ -537,6 +537,7 @@
grid-template-rows: 1fr min-content;
}
.blis-chatmodal_admin-controls {
padding: 1em;
}
.blis-chatmodal_modal-controls{
background-color: var(--color-secondary);
Expand All @@ -563,3 +564,17 @@
.ms-Button--primary:hover {
background-color: var(--color-primary-darker) !important;
}


/** Log Dialog Admin */
.log-dialog-admin {
display: grid;
grid-gap: 1rem;
}
.log-dialog-admin__title {
font-weight: bold;
border-bottom: 1px solid black;
}
.log-dialog-admin__content {
padding-bottom: 1em;
}
88 changes: 88 additions & 0 deletions src/containers/LogDialogAdmin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';
import { returntypeof } from 'react-redux-typescript';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { State } from '../types'
import ExtractorResponseEditor from './ExtractorResponseEditor';
import { Activity, Message } from 'botframework-directlinejs'
import { LogDialog, LogRound, LogScorerStep, ActionBase, EntityBase } from 'blis-models'

class LogDialogAdmin extends React.Component<Props, any> {
findRoundAndScorerStep(logDialog: LogDialog, activity: Activity): { round: LogRound, scorerStep: LogScorerStep } {
// TODO: Add roundIndex and scoreIndex to activity instead of hiding within id if these are needed as first class properties.
const [roundIndex, scoreIndex] = activity.id.split(":").map(s => parseInt(s))

if (roundIndex > logDialog.rounds.length) {
throw new Error(`Index out of range: You are attempting to access round by index: ${roundIndex} but there are only: ${logDialog.rounds.length} rounds.`)
}

const round = logDialog.rounds[roundIndex]

if (scoreIndex > round.scorerSteps.length) {
throw new Error(`Index out of range: You are attempting to access scorer step by index: ${scoreIndex} but there are only: ${round.scorerSteps.length} scorere steps.`)
}

const scorerStep = round.scorerSteps[scoreIndex]

return {
round,
scorerStep
}
}

render() {
let round: LogRound = null
let scorerStep: LogScorerStep = null
let action: ActionBase = null
let entities: EntityBase[] = []

if (this.props.logDialog && this.props.selectedActivity) {
const result = this.findRoundAndScorerStep(this.props.logDialog, this.props.selectedActivity)
round = result.round
scorerStep = result.scorerStep
action = this.props.actions.find(action => action.actionId == scorerStep.predictedAction)
entities = this.props.entities.filter(entity => scorerStep.input.filledEntities.includes(entity.entityId))
}

return (
<div className="log-dialog-admin ms-font-l">
<div className="log-dialog-admin__title">Entity Detection</div>
<div className="log-dialog-admin__content">
{round
? <ExtractorResponseEditor isPrimary={true} isValid={true} extractResponse={round.extractorStep} />
: "Select an activity"}
</div>
<div className="log-dialog-admin__title">Memory</div>
<div className="log-dialog-admin__content">
{entities.length !== 0 && entities.map(entity => <div key={entity.entityName}>{entity.entityName}</div>)}
</div>
<div className="log-dialog-admin__title">Action</div>
<div className="log-dialog-admin__content">
{action && action.payload}
</div>
</div>
);
}
}
const mapDispatchToProps = (dispatch: any) => {
return bindActionCreators({
}, dispatch);
}
const mapStateToProps = (state: State) => {
return {
actions: state.actions,
entities: state.entities
}
}

export interface ReceivedProps {
logDialog: LogDialog,
selectedActivity: Activity
}

// Props types inferred from mapStateToProps & dispatchToProps
const stateProps = returntypeof(mapStateToProps);
const dispatchProps = returntypeof(mapDispatchToProps);
type Props = typeof stateProps & typeof dispatchProps & ReceivedProps;

export default connect<typeof stateProps, typeof dispatchProps, ReceivedProps>(mapStateToProps, mapDispatchToProps)(LogDialogAdmin);
68 changes: 58 additions & 10 deletions src/containers/LogDialogModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
import * as React from 'react';
import { returntypeof } from 'react-redux-typescript';
import { editEntityAsync } from '../actions/updateActions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react';
import { State } from '../types';
import { Modal } from 'office-ui-fabric-react/lib/Modal';
import Webchat from './Webchat'
import TrainDialogAdmin from './TrainDialogAdmin'
import LogDialogAdmin from './LogDialogAdmin'
import { Activity } from 'botframework-directlinejs'
import { BlisAppBase, LogDialog } from 'blis-models'
import { createChatSessionAsync } from '../actions/createActions'
import { BlisAppBase, LogDialog, Session } from 'blis-models'
import { deleteLogDialogAsync } from '../actions/deleteActions'

class LogDialogModal extends React.Component<Props, any> {

generateHistory() : Activity[] {
interface ComponentState {
selectedActivity: Activity | null,
chatSession: Session
}

class LogDialogModal extends React.Component<Props, ComponentState> {

componentWillMount() {
this.setState({
selectedActivity: null,
chatSession : new Session({saveToLog : true})
}, () => {
this.props.createChatSessionAsync(this.props.user.key, this.state.chatSession, this.props.apps.current.appId);
})
}

/**
* When modal is opened create a new session and reset the state.
*/
componentWillReceiveProps(nextProps: Props) {
if (this.props.open === false && nextProps.open === true) {
this.setState({
selectedActivity: null,
chatSession : new Session({saveToLog : true})
}, () => {
this.props.createChatSessionAsync(this.props.user.key, this.state.chatSession, this.props.apps.current.appId);
})
}
}

generateHistory(): Activity[] {
if (!this.props.logDialog) {
return [];
}
Expand All @@ -36,7 +64,7 @@ class LogDialogModal extends React.Component<Props, any> {
let action = actions.filter(a => a.actionId === scorerStep.predictedAction)[0]
return {
id: `${i}:${j}`,
from:{
from: {
id: "BlisTrainer",
name: "BlisTrainer"
},
Expand All @@ -56,6 +84,16 @@ class LogDialogModal extends React.Component<Props, any> {
this.props.onClose()
}

onSelectWebChatActivity(activity: Activity) {
this.setState({
selectedActivity: activity
})
}

onPostWebChatActivity(activity: Activity) {
console.log(`activity posted: `, activity)
}

render() {
let history = this.generateHistory();
return (
Expand All @@ -67,11 +105,19 @@ class LogDialogModal extends React.Component<Props, any> {
>
<div className="blis-chatmodal">
<div className="blis-chatmodal_webchat">
<Webchat sessionType={"chat"} history={history} />
<Webchat
sessionType={"chat"}
history={history}
onSelectActivity={activity => this.onSelectWebChatActivity(activity)}
onPostActivity={activity => this.onPostWebChatActivity(activity)}
/>
</div>
<div className="blis-chatmodal_controls">
<div className="blis-chatmodal_admin-controls">
{/* <TrainDialogAdmin /> */}
<LogDialogAdmin
logDialog={this.props.logDialog}
selectedActivity={this.state.selectedActivity}
/>
</div>
<div className="blis-chatmodal_modal-controls">
<PrimaryButton
Expand All @@ -95,11 +141,13 @@ class LogDialogModal extends React.Component<Props, any> {
}
const mapDispatchToProps = (dispatch: any) => {
return bindActionCreators({
createChatSessionAsync,
deleteLogDialogAsync
}, dispatch);
}
const mapStateToProps = (state: State, ownProps: ReceivedProps) => {
return {
apps: state.apps,
user: state.user,
actions: state.actions
}
Expand All @@ -117,4 +165,4 @@ const stateProps = returntypeof(mapStateToProps);
const dispatchProps = returntypeof(mapDispatchToProps);
type Props = typeof stateProps & typeof dispatchProps & ReceivedProps;

export default connect(mapStateToProps, mapDispatchToProps)(LogDialogModal);
export default connect<typeof stateProps, typeof dispatchProps, ReceivedProps>(mapStateToProps, mapDispatchToProps)(LogDialogModal);
2 changes: 1 addition & 1 deletion src/containers/TrainDialogAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class TrainDialogAdmin extends React.Component<Props, any> {
details = [];
for (let entityId of entityIds) {
let entity: EntityBase = this.props.entities.find((a: EntityBase) => a.entityId == entityId);
details.push(<div className='ms-font-l'>{entity.entityName}</div>);
details.push(<div className='ms-font-l' key={entity.entityName}>{entity.entityName}</div>);
}
}
return (
Expand Down
21 changes: 20 additions & 1 deletion src/containers/Webchat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class Webchat extends React.Component<Props, any> {
private behaviorSubject : BehaviorSubject<any> = null;
private chatProps : BotChat.ChatProps = null;

static defaultProps = {
onSelectActivity: () => {},
onPostActivity: () => {}
}

constructor(p: any) {
super(p);
this.behaviorSubject = null;
Expand All @@ -29,7 +34,15 @@ class Webchat extends React.Component<Props, any> {
this.behaviorSubject = new BehaviorSubject<any>({});
this.behaviorSubject.subscribe((value) => {
if (value.activity) {
const activity: Activity = value.activity
this.props.onSelectActivity(activity)

// TODO: Remove split of id here.
// This is coupling knowledge about how ID was constructed within the generateHistory function
// Id should be an opaque and unique identifier.
let [roundNum, scoreNum] = value.activity.id.split(":");

// TODO: Remove hard coding of train related actions from web chat
this.props.setTrainDialogView(roundNum, scoreNum);
}
})
Expand All @@ -50,6 +63,8 @@ class Webchat extends React.Component<Props, any> {
const _dl = {
...dl,
postActivity: (activity: any) => {
// TODO: Remove hard coding of adding message to stack
// Webchat should not be aware of what happens when activity is posted, only that is is posted.
if (activity.type = "message")
{
if (this.props.sessionType === 'teach') {
Expand All @@ -63,6 +78,8 @@ class Webchat extends React.Component<Props, any> {
} else {
this.props.addMessageToChatConversationStack(activity)
}

this.props.onPostActivity(activity)
}
return dl.postActivity(activity)
},
Expand Down Expand Up @@ -113,7 +130,9 @@ const mapStateToProps = (state: State, ownProps: any) => {

interface ReceivedProps {
sessionType: string,
history: Activity[]
history: Activity[],
onSelectActivity: (a: Activity) => void,
onPostActivity: (a: Activity) => void
}

// Props types inferred from mapStateToProps & dispatchToProps
Expand Down

0 comments on commit bc13b14

Please sign in to comment.