declare type MentionType= "PRONOMINAL" | "NOMINAL" | "PROPER" | "LIST"; declare interface Mention { index: number; start: number; end: number; utterance: number; type: MentionType; text: string; } declare interface Coreference { original: string; resolved: string; } declare interface Response { cleanedText: string; corefResText: string; coreferences: Coreference[]; mentions: Mention[]; singleScores: { [id: number]: number | null }; /// Is this mention likely to be a single mention (w/o any corefs). `id` is a Mention's `index` pairScores: { [id: number]: { [id: number]: number } }; /// Pair-wise score, in `{ from: { to: ... } }` format. Non-directed arcs. /// Single scores are to be compared to the set of pairScores (for the same mention). /// If it's higher than every pair score, it's a single mention. cleanedContext: string; /// Cleaned version of the context. isResolved: boolean; } class Coref { endpoint: string; onStart = () => {}; onSuccess = () => {}; container?: HTMLElement; svgContainer?: SVGSVGElement; constructor(endpoint: string, opts: any) { this.endpoint = endpoint; if (opts.onStart) { (this).onStart = opts.onStart; } if (opts.onSuccess) { (this).onSuccess = opts.onSuccess; } window.addEventListener('resize', this.svgResize); } svgResize() { if (!this.container || !this.svgContainer) { return ; } this.svgContainer.setAttribute('width', `${this.container.scrollWidth}`); /// Caution: not offsetWidth. this.svgContainer.setAttribute('height', `${this.container.scrollHeight}`); } parse(text: string) { this.onStart(); const path = `${this.endpoint}?text=${encodeURIComponent(text)}`; const request = new XMLHttpRequest(); request.open('GET', path); request.onload = () => { if (request.status >= 200 && request.status < 400) { this.onSuccess(); const res: Response = JSON.parse(request.responseText); this.render(res); } else { console.error('Error', request); } }; request.send(); } render(res: Response) { const mentions = (res).mentions; // We will sort them in Displacy for (const m of mentions) { // Let's add each mention's singleScore m.singleScore = res.singleScores[m.index] || undefined; } const markup = Displacy.render(res.cleanedText, mentions); if (!this.container || !this.svgContainer) { return ; } this.container.innerHTML = `
${markup}
`; /// SVG this.svgContainer.textContent = ""; // Empty this.svgResize(); (window).container = this.container; (window).svgContainer = this.svgContainer; /** * Arrows preparation */ const endY = document.querySelector('.container .text')!.getBoundingClientRect().top - this.container.getBoundingClientRect().top - 2; SvgArrow.yArrows = endY; /** * Render arrows */ for (const [__from, scores] of Object.entries(res.pairScores)) { const from = parseInt(__from, 10); /// Convert all string keys to ints... for (const [__to, score] of Object.entries(scores)) { const to = parseInt(__to, 10); // Positions: const markFrom = document.querySelector(`mark[data-index="${from}"]`) as HTMLElement; const markTo = document.querySelector(`mark[data-index="${to}"]`) as HTMLElement; // console.log(markFrom, markTo, score); // todo remove const arrow = new SvgArrow(this.container, markFrom, markTo, score); // Is this a resolved coref? if (score >= Math.max(...Object.values(scores))) { arrow.classNames.push('score-ok'); // Best pairwise score // Is it the better than the singleScore? const singleScore = res.singleScores[from]; if (singleScore && score >= singleScore) { arrow.classNames.push('score-best'); } } this.svgContainer.appendChild(arrow.generate()); } } } }