import React from 'react' ; import styled from 'styled-components' ; import Popover from 'antd/es/popover' ; import { WithLogScaleSlider , NestedHighlight , getHighlightColor , HighlightColor , ClusterMap , } from '@allenai/tugboat/components' ; import { Output } from '@allenai/tugboat/components/form' ; import { ModuleInfo , DEFAULT_MIN_ATTN } from './ModuleInfo' ; import { Input , Step } from './Explanation' ; interface StepOutputProps { inputs : Input [ ] ; step : Step ; } export const StepOutput = ( { inputs , step } ) => { const moduleInfo = ModuleInfo . findInfoByName ( step . moduleName ) ; const range = [ 0 , 1 ] ; const values = [ 1e-8 , 1 ] ; const nf = Intl . NumberFormat ( 'en-US' , { maximumSignificantDigits : 4 } ) ; const highlightColorByLabel = { } ; let colorIdx = - 1 ; return ( < > { moduleInfo ? ( < Info > < strong > { moduleInfo . signature } < /strong> {moduleInfo.description} < /Info>) : null} < WithLogScaleSlider label = "Minimum Attention" range = { range } values = { values } defaultValue = { moduleInfo && moduleInfo . defaultMinAttn ? moduleInfo . defaultMinAttn : DEFAULT_MIN_ATTN } > { ( min ) => inputs . map ( ( input , inputIdx ) => { const outputs = step . getOutputsForInput ( input . name ) ; const shouldMergeSpans = input . name === 'question' || input . name === 'passage' ; const clusters = { } ; const valuesByTokenIndex = { } ; for ( const output of outputs ) { const label = output . label || step . moduleName ; if ( highlightColorByLabel [ label ] === undefined ) { highlightColorByLabel [ label ] = getHighlightColor ( ++ colorIdx ) ; } const spans = [ ] ; const lenValues = output . values . length ; let start = null ; for ( const rawIdx in output . values ) { const index = parseInt ( rawIdx ) ; const value = output . values [ index ] ; if ( ! Array . isArray ( valuesByTokenIndex [ index ] ) ) { valuesByTokenIndex [ index ] = [ ] ; } valuesByTokenIndex [ index ] . push ( { label , value } ) ; if ( shouldMergeSpans ) { if ( value >= min ) { if ( start === null ) { start = index ; } if ( index === lenValues - 1 && start !== null ) { spans . push ( [ start , index ] ) ; start = null ; } } else { if ( start !== null ) { spans . push ( [ start , index - 1 ] ) ; start = null ; } } } else { if ( value >= min ) { spans . push ( [ index , index ] ) ; } } } if ( Array . isArray ( clusters [ label ] ) ) { clusters [ label ] = clusters [ label ] . concat ( spans ) ; } else { clusters [ label ] = spans ; } } const tokenSeparator = input . name === 'question' || input . name === 'passage' ? undefined : ( < > , & nbsp ; < />); return ( < Output . SubSection key = { `${ inputIdx }/${ input . name }` } title = { input . name . charAt ( 0 ) . toUpperCase ( ) + input . name . replace ( '_' , ' ' ) . slice ( 1 ) } > < SpacedNestedHighlight highlightColor = { ( token ) => highlightColorByLabel [ token . cluster ] } tokens = { input . tokens . map ( ( t , i ) => { const valuesForIdx = valuesByTokenIndex [ i ] ; if ( ! Array . isArray ( valuesForIdx ) || valuesForIdx . length === 0 ) { return t ; } const content = ( < > { valuesForIdx . map ( ( { label , value } , i ) => ( < div key = { `${ i }/${ label }` } > < strong > { label } : < /strong>&nbsp; { nf . format ( value ) } < /div>))} < />); return ( < Popover key = { `${ i }/${ t }` } title = { t } content = { content } > { t } < /Popover>); } ) } clusters = { clusters } tokenSeparator = { tokenSeparator } / > < /Output.SubSection>); } ) } < /WithLogScaleSlider> < />); } ; const Info = styled . div `
    ${ ( { theme } ) => `
    padding: ${ theme . spacing . sm };
    border: 1px solid ${ theme . palette . border . info };
    border-radius: ${ theme . shape . borderRadius . default };
    background: ${ theme . palette . background . info };
    color: ${ theme . palette . text . info };
    margin: 0 0 ${ theme . spacing . md };
  ` }
` ; const SpacedNestedHighlight = styled ( NestedHighlight ) `
    padding: 0;
    margin: 0 0 ${ ( { theme } ) => theme . spacing . lg } 0;
` ;