| import type { BpeMergeReason, FrontendAnalyzeResult, FrontendToken } from '../api/GLTR_API'; |
| import type { AttributionApiResponse } from './attributionResultCache'; |
| import { getDigitsMergeEnabled } from '../utils/digitsMergeManager'; |
| import { |
| getAttentionRawScore, |
| mergeAttentionTokensFullyForRendering, |
| normalizeTokenScores, |
| } from '../utils/semanticUtils'; |
|
|
| |
| export type ExcludeRegexMatchRegion = { |
| start: number; |
| end: number; |
| }; |
|
|
| export type AttributionDisplayOptions = { |
| colorRangeMax: number | null; |
| |
| excludePromptPatternsText: string; |
| |
| |
| |
| excludePromptPatternsRegion?: ExcludeRegexMatchRegion; |
| }; |
|
|
| function mapNormedScoresToColorRange(rawScoresNormed: number[], x: number): number[] { |
| return rawScoresNormed.map((s) => (s > x ? 1 : s / x)); |
| } |
|
|
| |
| const EXCLUDE_REGEX_LINE_COMMENT_MARKER = '#comment#'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function collectExcludeRegexMatchIntervals( |
| context: string, |
| excludeMultiline: string, |
| region?: ExcludeRegexMatchRegion |
| ): [number, number][] { |
| const r0 = region?.start ?? 0; |
| const r1 = region?.end ?? context.length; |
| const lo = Math.max(0, Math.min(r0, context.length)); |
| const hi = Math.max(lo, Math.min(r1, context.length)); |
| const slice = context.slice(lo, hi); |
|
|
| const intervals: [number, number][] = []; |
| for (const rawLine of excludeMultiline.split('\n')) { |
| const cut = rawLine.indexOf(EXCLUDE_REGEX_LINE_COMMENT_MARKER); |
| const line = cut === -1 ? rawLine : rawLine.slice(0, cut); |
| if (line === '') continue; |
| try { |
| const re = new RegExp(line, 'g'); |
| for (const m of slice.matchAll(re)) { |
| if (m.index === undefined) continue; |
| const abs = lo + m.index; |
| intervals.push([abs, abs + m[0].length]); |
| } |
| } catch { |
| |
| } |
| } |
| return intervals; |
| } |
|
|
| |
| export function isOffsetSpanFullyExcluded(ts: number, te: number, intervals: [number, number][]): boolean { |
| for (const [a, b] of intervals) { |
| if (a <= ts && te <= b) return true; |
| } |
| return false; |
| } |
|
|
| |
| |
| |
| |
| export function buildAttributionDisplayResult( |
| context: string, |
| response: AttributionApiResponse, |
| options: AttributionDisplayOptions |
| ): FrontendAnalyzeResult { |
| const tokens = response.token_attribution ?? []; |
| const region = options.excludePromptPatternsRegion ?? { start: 0, end: context.length }; |
| const excludeIntervals = collectExcludeRegexMatchIntervals( |
| context, |
| options.excludePromptPatternsText, |
| region |
| ); |
|
|
| const originalTokens: FrontendToken[] = tokens.map((t) => ({ |
| raw: t.raw, |
| offset: t.offset, |
| pred_topk: [] |
| })); |
|
|
| const effective = tokens.map((t) => { |
| const [ts, te] = t.offset; |
| const excluded = isOffsetSpanFullyExcluded(ts, te, excludeIntervals); |
| return { |
| offset: t.offset, |
| raw: t.raw, |
| score: excluded ? 0 : t.score, |
| }; |
| }); |
|
|
| const merged = mergeAttentionTokensFullyForRendering(effective, context, { |
| digitMerge: getDigitsMergeEnabled(), |
| }); |
| const normalized = normalizeTokenScores(merged); |
|
|
| const digitMergedTokens: FrontendToken[] = normalized.map((t) => { |
| const m = (t as { bpe_merged?: BpeMergeReason }).bpe_merged; |
| const parts = (t as { bpe_merge_parts?: string[] }).bpe_merge_parts; |
| const row: FrontendToken = { |
| offset: t.offset, |
| raw: t.raw, |
| pred_topk: [], |
| }; |
| if (m !== undefined) { |
| row.bpe_merged = m; |
| } |
| if (parts !== undefined) { |
| row.bpe_merge_parts = [...parts]; |
| } |
| return row; |
| }); |
|
|
| const attentionRawScores = normalized.map((t) => getAttentionRawScore(t)); |
| const rawScoresNormed = normalized.map((t) => t.score); |
|
|
| const result = { |
| model: response.model ?? null, |
| error: null, |
| bpe_strings: digitMergedTokens, |
| originalTokens, |
| bpeBpeMergedTokens: digitMergedTokens.map((t) => ({ ...t })), |
| originalText: context |
| } as FrontendAnalyzeResult; |
|
|
| const ext = result as FrontendAnalyzeResult & { |
| rawScoresNormed: number[]; |
| colorScores?: number[]; |
| attentionRawScores: number[]; |
| }; |
| ext.rawScoresNormed = rawScoresNormed; |
| ext.attentionRawScores = attentionRawScores; |
| if (options.colorRangeMax != null) { |
| ext.colorScores = mapNormedScoresToColorRange(rawScoresNormed, options.colorRangeMax); |
| } |
|
|
| return result; |
| } |
|
|