Spaces:
Sleeping
Sleeping
File size: 4,650 Bytes
649ee03 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | export interface PIIEntity {
category: string
kind: 'text' | 'visual'
bbox: [number, number, number, number]
l1: string
text: string | null
score: number | null
}
export interface TextPIISpan {
category: string
start: number
end: number
l1: string
text: string
score: number | null
}
export interface EntityTaxonomy {
text: string[]
visual: string[]
}
export type RedactMode = 'solid' | 'blur' | 'pixelate'
async function assertOk(res: Response): Promise<Response> {
if (!res.ok) {
const body = await res.text().catch(() => '')
throw new Error(`${res.status} ${res.statusText}${body ? `: ${body}` : ''}`)
}
return res
}
export async function getHealth(): Promise<{ status: string; device: string }> {
const res = await assertOk(await fetch('/health'))
return res.json()
}
export async function getEntities(): Promise<EntityTaxonomy> {
const res = await assertOk(await fetch('/entities'))
return res.json()
}
export async function getTextEntities(): Promise<{ text: string[] }> {
const res = await assertOk(await fetch('/entities-text'))
return res.json()
}
export async function detect(file: File, categories?: string[]): Promise<PIIEntity[]> {
const form = new FormData()
form.append('file', file)
const qs = categories?.length ? `?categories=${encodeURIComponent(categories.join(','))}` : ''
const res = await assertOk(await fetch(`/detect${qs}`, { method: 'POST', body: form }))
const data = await res.json()
return data.entities as PIIEntity[]
}
function rgbToHex([r, g, b]: [number, number, number]): string {
return [r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')
}
export async function redact(
file: File,
mode: RedactMode,
color: [number, number, number],
categories?: string[],
): Promise<Blob> {
const form = new FormData()
form.append('file', file)
form.append('mode', mode)
form.append('color', rgbToHex(color))
const qs = categories?.length ? `?categories=${encodeURIComponent(categories.join(','))}` : ''
const res = await assertOk(await fetch(`/redact${qs}`, { method: 'POST', body: form }))
return res.blob()
}
export async function detectText(text: string, categories?: string[]): Promise<TextPIISpan[]> {
const res = await assertOk(
await fetch('/detect-text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, categories }),
}),
)
const data = await res.json()
return data.spans as TextPIISpan[]
}
// label -> {pseudonym: original} plus per-label counters; opaque to the UI
// beyond rendering the entries table.
export interface PseudonymMapping {
entries: Record<string, Record<string, string>>
counters: Record<string, number>
}
async function postJson(path: string, body: unknown): Promise<any> {
const res = await assertOk(
await fetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}),
)
return res.json()
}
export async function redactText(text: string, categories?: string[]): Promise<string> {
const data = await postJson('/redact-text', { text, categories, mask: '[{category}]' })
return data.text
}
export type DeidStrategy = 'counter' | 'hash'
export async function deidentifyText(
text: string,
categories?: string[],
strategy: DeidStrategy = 'counter',
): Promise<{ text: string; mapping: PseudonymMapping }> {
return postJson('/deidentify-text', { text, categories, strategy })
}
export async function anonymizeText(text: string, categories?: string[]): Promise<string> {
const data = await postJson('/anonymize-text', { text, categories })
return data.text
}
export async function deidentifyImage(
file: File,
categories?: string[],
strategy: DeidStrategy = 'counter',
): Promise<{ imageUrl: string; mapping: PseudonymMapping }> {
const form = new FormData()
form.append('file', file)
const params = new URLSearchParams({ strategy })
if (categories?.length) params.set('categories', categories.join(','))
const res = await assertOk(
await fetch(`/deidentify?${params}`, { method: 'POST', body: form }),
)
const data = await res.json()
return { imageUrl: `data:image/png;base64,${data.image}`, mapping: data.mapping }
}
export async function anonymizeImage(file: File, categories?: string[]): Promise<Blob> {
const form = new FormData()
form.append('file', file)
const qs = categories?.length ? `?categories=${encodeURIComponent(categories.join(','))}` : ''
const res = await assertOk(await fetch(`/anonymize${qs}`, { method: 'POST', body: form }))
return res.blob()
}
|