Spaces:
Running
Running
Download render.js from batmac/ai-text-detector-web: direct link, hf CLI and curl.
- Browser
- Download file 3.39 kB
-
https://huggingface.co/spaces/batmac/ai-text-detector-web/resolve/main/render.js
- Command line
-
hf download hf://spaces/batmac/ai-text-detector-web/render.js
-
curl -L -o render.js https://huggingface.co/spaces/batmac/ai-text-detector-web/resolve/main/render.js
3.39 kB
| import { bar, probColor } from './scoring.js'; | |
| // Locale-independent thousands grouping, matching the Python app's f"{n:,}". | |
| export const formatNumber = (n) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); | |
| const escapeHtml = (s) => | |
| s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | |
| export function highlightHtml(text, spans, scores) { | |
| const parts = ['<div class="aid-highlight">']; | |
| let cursor = 0; | |
| spans.forEach((span, i) => { | |
| parts.push(escapeHtml(text.slice(cursor, span.start)).replace(/\n/g, '<br>')); | |
| const score = scores[i]; | |
| parts.push( | |
| `<span title="P(AI) = ${score.toFixed(3)}" data-p="${score.toFixed(3)}" ` + | |
| `style="background:${probColor(score)}; border-radius:3px; padding:1px 2px;">` + | |
| `${escapeHtml(span.sentence)}</span>`, | |
| ); | |
| cursor = span.end; | |
| }); | |
| parts.push(escapeHtml(text.slice(cursor)).replace(/\n/g, '<br>')); | |
| parts.push('</div>'); | |
| return parts.join(''); | |
| } | |
| export function reportMarkdown( | |
| result, | |
| { threshold, aggregation, modelRepo, device, dtype, chars }, | |
| ) { | |
| const label = aggregation === 'max' ? 'Max' : 'Mean'; | |
| const note = | |
| result.windows === 1 | |
| ? 'one window, so mean and max are identical' | |
| : aggregation === 'max' | |
| ? 'highest-scoring window' | |
| : `average across ${result.windows} windows`; | |
| const scores = result.sentences.map((s) => s.score); | |
| const atOrAbove = scores.filter((s) => s >= threshold).length; | |
| return [ | |
| '# AI Text Detection Report', | |
| '', | |
| `**Model:** \`${modelRepo}\``, | |
| `**Backend:** ${dtype} ONNX on ${device}`, | |
| `**Characters:** ${formatNumber(chars)} | **Sentences:** ${scores.length} | **Windows scored:** ${result.windows}`, | |
| `**Aggregation:** ${label} (${note}) | **Threshold:** ${threshold.toFixed(2)}`, | |
| '', | |
| '## Verdict', | |
| '', | |
| `### ${result.verdict}`, | |
| '', | |
| `\`${bar(result.score)}\` **score = ${result.score.toFixed(3)}** (${result.aboveThreshold ? 'above' : 'below'} ${threshold.toFixed(2)})`, | |
| '', | |
| '| Metric | Value |', | |
| '| --- | --- |', | |
| `| Score (${label} of windows) | ${result.score.toFixed(3)} |`, | |
| `| Mean of windows | ${result.scoreMean.toFixed(3)} |`, | |
| `| Max of windows | ${result.scoreMax.toFixed(3)} |`, | |
| `| Sentences at or above threshold | ${atOrAbove}/${scores.length} |`, | |
| `| Max sentence score | ${Math.max(...scores).toFixed(3)} |`, | |
| `| Min sentence score | ${Math.min(...scores).toFixed(3)} |`, | |
| `| Decision threshold | ${threshold.toFixed(2)} |`, | |
| '', | |
| '## Sentence-level breakdown', | |
| '', | |
| ...result.sentences.map((s) => { | |
| const snippet = s.sentence.length <= 160 ? s.sentence : `${s.sentence.slice(0, 157)}...`; | |
| return `- \`${s.score.toFixed(3)}\` \`${bar(s.score, 12)}\` ${snippet}`; | |
| }), | |
| '', | |
| '## Most AI-like passages', | |
| '', | |
| ...[...result.sentences] | |
| .sort((a, b) => b.score - a.score) | |
| .slice(0, 5) | |
| .map((s) => `> **${s.score.toFixed(3)}** - ${s.sentence}`), | |
| '', | |
| '---', | |
| '', | |
| '_Scores come from an ONNX model running in your browser, and nothing is uploaded. They are probabilities, not proof: the model card warns against using this detector as the sole basis for high-stakes decisions. The threshold is a display choice, only 0.50 comes from the model card, and no false-positive rate is claimed for any other value._', | |
| ].join('\n'); | |
| } |