| |
| |
| |
| |
|
|
| import type { BpeMergeReason, FrontendToken } from '../api/GLTR_API'; |
| import { tr, trf } from '../lang/i18n-lite'; |
| import { escapeHtml, tooltipTokenDisplayHtml } from './tokenDisplayUtils'; |
| import { |
| prepareTopkDisplayRows, |
| renderTopkChartHtml, |
| topkDisplaySelection, |
| } from './topkChartUtils'; |
|
|
| const DISPLAY_TOPK = 10; |
|
|
| function bpeMergedInfoMessage(reason: BpeMergeReason): string { |
| switch (reason) { |
| case 'overlap': |
| return tr('BPE overlap merge: overlapping spans were combined.'); |
| case 'digit': |
| return tr('Digit merge: adjacent digit sub-tokens were combined.'); |
| } |
| } |
|
|
| const MERGE_PARTS_TOOLTIP_MAX = 8; |
|
|
| |
| function bpeMergePartsTooltipHtml(parts: string[] | undefined): string { |
| if (!parts?.length) return ''; |
| const shown = parts.slice(0, MERGE_PARTS_TOOLTIP_MAX); |
| const hidden = parts.length - shown.length; |
| const body = shown.map((p) => tooltipTokenDisplayHtml(p)).join('<br/>'); |
| const more = |
| hidden > 0 |
| ? `<br/>${escapeHtml(trf('(+{n} more)', { n: hidden }))}` |
| : ''; |
| return ( |
| `<div class="topk-chart-info-parts">` + |
| `${escapeHtml(trf('Source fragments ({count}):', { count: parts.length }))}<br/>${body}${more}` + |
| `</div>` |
| ); |
| } |
|
|
| |
| |
| |
| |
| export function getFrontendTokenTopkState(tokenData: FrontendToken): { |
| predTopk: [string, number][]; |
| isPlaceholderTopk: boolean; |
| hasRealTopk: boolean; |
| } { |
| const predTopk = tokenData.pred_topk ?? []; |
| const isPlaceholderTopk = |
| tokenData.real_topk != null && |
| Array.isArray(tokenData.real_topk) && |
| tokenData.real_topk[1] === 1 && |
| predTopk.length === 0; |
| const hasRealTopk = |
| tokenData.real_topk != null && Array.isArray(tokenData.real_topk) && !isPlaceholderTopk; |
| return { predTopk, isPlaceholderTopk, hasRealTopk }; |
| } |
|
|
| |
| export type TooltipPredictionsExtraOptions = { |
| interactive?: boolean; |
| highlightToken?: string; |
| |
| highlightProb?: number; |
| }; |
|
|
| |
| |
| |
| export function buildTooltipPredictionsInnerHtml( |
| tokenData: FrontendToken | null | undefined, |
| extra?: TooltipPredictionsExtraOptions |
| ): string { |
| if (!tokenData) return ''; |
|
|
| const { predTopk, hasRealTopk } = getFrontendTokenTopkState(tokenData); |
| const hasPredictions = predTopk.length > 0; |
|
|
| if (!hasPredictions && tokenData.bpe_merged == null) { |
| return ''; |
| } |
| if (tokenData.bpe_merged != null) { |
| const partsHtml = bpeMergePartsTooltipHtml(tokenData.bpe_merge_parts); |
| return ( |
| `<div class="row info-row topk-chart-info-row">` + |
| `<div class="topk-chart-info-msg">${bpeMergedInfoMessage(tokenData.bpe_merged)}</div>` + |
| partsHtml + |
| `</div>` |
| ); |
| } |
| if (!hasPredictions) { |
| return ( |
| `<div class="row info-row topk-chart-info-row">` + |
| `<div class="topk-chart-info-msg">${tr('Top-k data not available.')}</div>` + |
| `</div>` |
| ); |
| } |
|
|
| let topkData = predTopk.slice(0, DISPLAY_TOPK).map(([token, prob]) => ({ token, prob })); |
| const selection = |
| hasRealTopk && tokenData.real_topk != null |
| ? topkDisplaySelection(tokenData.raw, tokenData.real_topk[1]) |
| : undefined; |
| topkData = prepareTopkDisplayRows(topkData, selection); |
| const highlight = extra?.highlightToken ?? tokenData.raw; |
| let selectedProb: number | undefined; |
| if (extra?.highlightProb != null && Number.isFinite(extra.highlightProb)) { |
| selectedProb = extra.highlightProb; |
| } else if (hasRealTopk && highlight === tokenData.raw && tokenData.real_topk != null) { |
| selectedProb = tokenData.real_topk[1]; |
| } else { |
| const row = predTopk.find(([t]) => t === highlight); |
| if (row) selectedProb = row[1]; |
| } |
| return renderTopkChartHtml(topkData, { |
| selectedToken: highlight, |
| selectedProb, |
| interactivePickable: extra?.interactive === true, |
| }); |
| } |
|
|