Files
market/server/routes/og/persona.png.ts
zeekay 00c546139e fix build errors: broken identifier and remaining soul→persona in server/
- Fix space in identifier: Hanzo BotIcon → HanzoBotIcon
- Rename Soul types/functions in server OG files to Persona
- Fix import path: soulOgSvg → personaOgSvg
- Fix API response type: soul? → persona?
2026-02-17 21:32:33 -08:00

112 lines
3.1 KiB
TypeScript

import { initWasm, Resvg } from '@resvg/resvg-wasm'
import { defineEventHandler, getQuery, getRequestHost, setHeader } from 'h3'
import type { PersonaOgMeta } from '../../og/fetchPersonaOgMeta'
import { fetchPersonaOgMeta } from '../../og/fetchPersonaOgMeta'
import {
FONT_MONO,
FONT_SANS,
getFontBuffers,
getMarkDataUrl,
getResvgWasm,
} from '../../og/ogAssets'
import { buildPersonaOgSvg } from '../../og/personaOgSvg'
type OgQuery = {
slug?: string
owner?: string
version?: string
title?: string
description?: string
v?: string
}
let wasmInitPromise: Promise<void> | null = null
function cleanString(value: unknown) {
if (typeof value !== 'string') return ''
return value.trim()
}
function getApiBase(eventHost: string | null) {
const direct = (process.env.VITE_API_URL ?? '').trim()
if (direct) return direct
const site = process.env.SITE_URL?.trim() || process.env.VITE_SITE_URL?.trim()
if (site) return site
if (eventHost) return `https://${eventHost}`
return 'https://personas.hanzo.ai'
}
async function ensureWasm() {
if (!wasmInitPromise) {
wasmInitPromise = getResvgWasm().then((wasm) => initWasm(wasm))
}
await wasmInitPromise
}
function buildFooter(slug: string, owner: string | null) {
if (owner) return `@${owner}/${slug}`
return `personas/${slug}`
}
export default defineEventHandler(async (event) => {
const query = getQuery(event) as OgQuery
const slug = cleanString(query.slug)
if (!slug) {
setHeader(event, 'Content-Type', 'text/plain; charset=utf-8')
return 'Missing `slug` query param.'
}
const ownerFromQuery = cleanString(query.owner)
const versionFromQuery = cleanString(query.version)
const titleFromQuery = cleanString(query.title)
const descriptionFromQuery = cleanString(query.description)
const needFetch = !titleFromQuery || !descriptionFromQuery || !ownerFromQuery || !versionFromQuery
const meta: PersonaOgMeta | null = needFetch
? await fetchPersonaOgMeta(slug, getApiBase(getRequestHost(event)))
: null
const owner = ownerFromQuery || meta?.owner || ''
const version = versionFromQuery || meta?.version || ''
const title = titleFromQuery || meta?.displayName || slug
const description = descriptionFromQuery || meta?.summary || ''
const ownerLabel = owner ? `@${owner}` : 'PersonaHub'
const versionLabel = version ? `v${version}` : 'latest'
const footer = buildFooter(slug, owner || null)
const cacheKey = version ? 'public, max-age=31536000, immutable' : 'public, max-age=3600'
setHeader(event, 'Cache-Control', cacheKey)
setHeader(event, 'Content-Type', 'image/png')
const [markDataUrl, fontBuffers] = await Promise.all([
getMarkDataUrl(),
ensureWasm().then(() => getFontBuffers()),
])
const svg = buildPersonaOgSvg({
markDataUrl,
title,
description,
ownerLabel,
versionLabel,
footer,
})
const resvg = new Resvg(svg, {
fitTo: { mode: 'width', value: 1200 },
font: {
fontBuffers,
defaultFontFamily: FONT_SANS,
sansSerifFamily: FONT_SANS,
monospaceFamily: FONT_MONO,
},
})
const png = resvg.render().asPng()
resvg.free()
return png
})