quote-api / test-fixes.js
Quote API Deploy
feat: deploy quote-api to Hugging Face Spaces
2413e0a unverified
Raw
History Blame Contribute Delete
5.14 kB
// Numeric assertions for the group-avatar and media-scale fixes.
// Run: node test-fixes.js → exits non-zero on any failed assertion.
const assert = require('assert')
const { loadImage, createCanvas } = require('canvas')
const generateMethod = require('./methods/generate')
const QuoteGenerate = require('./utils/quote-generate')
// Count opaque pixels in the left avatar column (x < colW) of a PNG buffer.
function leftColumnInk (img, colW) {
const canvas = createCanvas(img.width, img.height)
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
const data = ctx.getImageData(0, 0, colW, img.height).data
let n = 0
for (let i = 3; i < data.length; i += 4) if (data[i] > 32) n++
return n
}
async function main () {
const scale = 2
const msg = (chatId, text) => ({
chatId,
avatar: true,
from: { id: chatId, name: `User ${chatId}`, photo: {} },
text
})
// 1. Same-sender group renders ONE avatar (on the last bubble); the
// two-sender variant renders two. Avatar circles are solid disks of
// d = 50·scale, so the grouped left column must hold roughly half the
// opaque pixels of the ungrouped one (±25% for the tail overlap).
const grouped = await generateMethod({ messages: [msg(7, 'перше'), msg(7, 'друге')], scale })
const ungrouped = await generateMethod({ messages: [msg(7, 'перше'), msg(8, 'друге')], scale })
assert.ok(!grouped.error && !ungrouped.error, 'generate failed')
const colW = 50 * scale
const inkGrouped = leftColumnInk(await loadImage(Buffer.from(grouped.image, 'base64')), colW)
const inkUngrouped = leftColumnInk(await loadImage(Buffer.from(ungrouped.image, 'base64')), colW)
const ratio = inkGrouped / inkUngrouped
assert.ok(ratio > 0.35 && ratio < 0.65,
`grouped/ungrouped avatar ink ratio ${ratio.toFixed(2)} — expected ≈ 0.5 (one avatar of two)`)
// 2. Voice renders as an in-bubble attachment row capped at ⅔ of the
// target width. The bubble width is fully deterministic:
// (avatar 50 + gap 10)·s + (rowW + 2·padX 16·s) + shadowPad 12·s,
// where rowW comes from the same drawVoiceRow inputs.
const { drawVoiceRow } = require('./utils/quote-generate/attachments')
const { NAME_COLORS_DARK } = require('./utils/quote-generate/constants')
const waveform = Array.from({ length: 60 }, (_, i) => (i * 7) % 32)
const qg = new QuoteGenerate('0:x')
const voice = await qg.generate('#1b1429', '#1b1429', {
from: { id: 1, name: 'V' },
voice: { waveform, duration: 42 },
avatar: false
}, 512, 512, scale, 'apple')
// from.id=1 → NAME_COLORS_DARK[1]; dark background → textColor #fff
const row = drawVoiceRow(waveform, 42, NAME_COLORS_DARK[1], '#fff', scale, 512 * scale * 2 / 3)
assert.ok(row.width <= Math.ceil(512 * scale * 2 / 3), `voice row ${row.width} exceeds ⅔ cap`)
const expectedW = (50 + 10) * scale + (row.width + 2 * 16 * scale) + 12 * scale
assert.ok(Math.abs(voice.width - expectedW) <= 2,
`voice bubble width ${voice.width} != expected ${expectedW}`)
// 3. Image mode: the wallpaper must contrast with the bubble, not blend.
// Sample the wallpaper corner vs the bubble interior; perceived
// brightness (BT.601) must differ noticeably for dark AND light themes.
const brightness = (p) => (p.r * 299 + p.g * 587 + p.b * 114) / 1000
for (const [bgColor, label] of [['#252e44', 'dark'], ['#e8ecf3', 'light']]) {
const img = await generateMethod({ messages: [msg(7, 'контраст фону')], scale, type: 'image', backgroundColor: bgColor })
assert.ok(!img.error, 'image generate failed')
const pic = await loadImage(Buffer.from(img.image, 'base64'))
const c = createCanvas(pic.width, pic.height)
const cx = c.getContext('2d')
cx.drawImage(pic, 0, 0)
const get = (x, y) => {
const d = cx.getImageData(x, y, 1, 1).data
return { r: d[0], g: d[1], b: d[2] }
}
// Wallpaper near the corner; bubble interior right of the avatar column
// ((50+10+16)·s + margin, vertical center).
const wallPx = get(8, 8)
const wall = brightness(wallPx)
const bubble = brightness(get(95 * scale / 2 + (50 + 10 + 20) * scale, Math.round(pic.height / 2)))
assert.ok(Math.abs(bubble - wall) >= 18,
`${label}: bubble (${bubble.toFixed(0)}) blends into wallpaper (${wall.toFixed(0)})`)
// Light wallpapers must be pastel, not gray: require visible chroma.
if (label === 'light') {
const chroma = Math.max(wallPx.r, wallPx.g, wallPx.b) - Math.min(wallPx.r, wallPx.g, wallPx.b)
assert.ok(chroma >= 15, `light wallpaper is gray (chroma ${chroma}) — expected a pastel tint`)
}
console.log(` image/${label}: bubble ${bubble.toFixed(0)} vs wallpaper ${wall.toFixed(0)}${Math.abs(bubble - wall).toFixed(0)})`)
}
console.log('OK: fixes assertions passed')
console.log(` avatar ink ratio grouped/ungrouped = ${ratio.toFixed(3)} (≈0.5)`)
console.log(` voice bubble width = ${voice.width} (expected ${expectedW}, row ${row.width})`)
}
main().catch((err) => {
console.error('FAIL:', err.message)
process.exit(1)
})