| import type { Page, Frame } from 'playwright'; |
| import { logger } from '../../utils/logger'; |
| import { readScopeDeepText } from '../selectors'; |
|
|
| type Scope = Page | Frame; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function parseSimilarityPercent(text: string): number | null { |
| const normalized = String(text || '').replace(/\s+/g, ' ').trim(); |
|
|
| |
| const labelled = normalized.match( |
| /(\d{1,3})\s*%\s*Overall Similarity/i, |
| ); |
| |
| const generic = normalized.match( |
| /(?:Overall\s+)?Similarity[:\s]+(\d{1,3})\s*%/i, |
| ); |
| |
| const reversed = normalized.match( |
| /Overall Similarity\s+(\d{1,3})\s*%/i, |
| ); |
| |
| const plain = normalized.match(/^(\d{1,3})\s*%?$/); |
|
|
| const value = Number( |
| labelled?.[1] ?? generic?.[1] ?? reversed?.[1] ?? plain?.[1] ?? NaN, |
| ); |
| if (!Number.isFinite(value)) return null; |
| return Math.max(0, Math.min(100, value)); |
| } |
|
|
| |
| |
| |
|
|
| const SCORE_SELECTORS = [ |
| |
| '.tii-SimilarityHeader__heading-content .tii-OverviewScore__Value', |
| '.tii-OverviewScore__Value', |
| 'span.tii-OverviewScore__Value', |
| |
| 'h1[aria-live="polite"] span', |
| 'h1[aria-live="polite"]', |
| |
| '.tii-SimilarityReportPanelHeader__OverviewScore span', |
| '.similarity-score', |
| '.overall-similarity-score', |
| '[data-testid="similarity-score"]', |
| |
| 'span[part="tii-grn-badge-label"]', |
| ]; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function readViewerSimilarityPercent( |
| page: Scope, |
| ): Promise<number | null> { |
| |
| |
| const MAX_READ_ATTEMPTS = 3; |
|
|
| for (let attempt = 1; attempt <= MAX_READ_ATTEMPTS; attempt++) { |
| |
| if (attempt > 1) { |
| await page.waitForTimeout(2000); |
| } |
|
|
| |
| for (const selector of SCORE_SELECTORS) { |
| const element = page.locator(selector).first(); |
|
|
| const rawText = await element |
| .textContent({ timeout: 2000 }) |
| .catch(() => null); |
|
|
| if (rawText !== null && rawText.trim() !== '') { |
| |
| let parsed = parseSimilarityPercent(rawText.trim()); |
| |
| if (parsed === null) { |
| parsed = parseSimilarityPercent(`${rawText.trim()} Overall Similarity`); |
| } |
|
|
| if (parsed !== null) { |
| logger.info('Read similarity score from viewer selector', { |
| selector, |
| rawText: rawText.trim(), |
| similarityPercent: parsed, |
| attempt, |
| }); |
| return parsed; |
| } |
| } |
| } |
|
|
| |
| const deepText = await readScopeDeepText(page); |
| if (deepText) { |
| const parsed = parseSimilarityPercent(deepText); |
| if (parsed !== null) { |
| logger.info('Read similarity score from viewer deep text', { |
| similarityPercent: parsed, |
| attempt, |
| }); |
| return parsed; |
| } |
| } |
|
|
| if (attempt < MAX_READ_ATTEMPTS) { |
| logger.debug(`Similarity score not found on attempt ${attempt}/${MAX_READ_ATTEMPTS}; retrying`); |
| } |
| } |
|
|
| logger.warn('Could not read similarity score from viewer after all attempts'); |
| return null; |
| } |
|
|