diff --git a/.env.example b/.env.example index b109542..f2ac417 100644 --- a/.env.example +++ b/.env.example @@ -36,8 +36,9 @@ PDL_API_KEY= GITHUB_TOKEN= # Finds a lead's photo (and their LinkedIn profile URL, as research) by -# searching their name, title and company. One image search per person, -# roughly a quarter of a cent each. Unset, only Gravatar supplies pictures. +# searching their name, title and company. One paid image search per person. +# Optional: crawls, GitHub, social profiles and Gravatar also supply pictures +# without image-search credits. LinkedIn image search requires this key. VALUESERP_API_KEY= # Ceiling on photo lookups per workspace per day. Default 300. PHOTO_LOOKUPS_PER_DAY= diff --git a/README.md b/README.md index 78eafe1..ccc030f 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,22 @@ typically yields three linked identities before any paid provider is touched. Each stage persists before the next runs, so a crash resumes rather than restarting, and a half-enriched prospect is still inspectable. +## Public profile photos + +Prospects, approval cards and the daily digest show a photo when a public source +supplies one. Crawls retain JSON-LD `Person.image` portraits and team images whose +alt text names the person, including lazy-loaded images and relative URLs. GitHub +enrichment keeps the public account's avatar. Photos are stored with the source +page in field provenance; existing photos are kept, and a later crawl or enrichment +can fill an empty photo even after image search previously missed. + +These paths use the pages and public API responses already being read. Gravatar +and social profile intake remain available, and `VALUESERP_API_KEY` optionally +enables the bounded image-search fallback for LinkedIn and company team pages. +Nothing signs into LinkedIn or bypasses a blocked page. Missing or unavailable +photos show initials. Previously imported people gain these photos on their next +crawl or enrichment; this change does not run a production backfill. + ## The three ideas worth knowing **The policy engine is arithmetic, not judgement.** Every outbound action diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 13bff0b..e144805 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -247,8 +247,8 @@ if (!encryptionKey) { /** * Pictures of leads, by search. * - * Optional, like every provider: unset, Gravatar is the only source and most - * people stay as initials. Each lookup is a paid request, so the sweep is + * Optional: crawls, public profile enrichment and Gravatar also supply photos. + * Each search lookup is a paid request, so the sweep is * capped per workspace per day as well as per tick. */ const photoFinder = process.env.VALUESERP_API_KEY @@ -257,7 +257,8 @@ const photoFinder = process.env.VALUESERP_API_KEY const photoLookupsPerDay = Number(process.env.PHOTO_LOOKUPS_PER_DAY ?? 300); -if (!photoFinder) console.log('no VALUESERP_API_KEY: lead photos come from Gravatar only'); +if (!photoFinder) + console.log('no VALUESERP_API_KEY: lead photos come from public profiles and crawls'); /** * The feed clients for one campaign's own targets. diff --git a/apps/web/components/avatar.tsx b/apps/web/components/avatar.tsx index 66a9ab3..824bcfc 100644 --- a/apps/web/components/avatar.tsx +++ b/apps/web/components/avatar.tsx @@ -1,3 +1,7 @@ +'use client'; + +import { useState } from 'react'; + /** * A face, or the initials standing in for one. * @@ -25,9 +29,10 @@ export function Avatar({ size?: keyof typeof SIZES; className?: string; }) { + const [failedSrc, setFailedSrc] = useState(); const box = `${SIZES[size]} shrink-0 rounded-full ${className}`; - if (src) { + if (src && src !== failedSrc) { return ( // eslint-disable-next-line @next/next/no-img-element -- remote hosts are not known ahead of time setFailedSrc(src)} className={`${box} bg-surface object-cover`} /> ); diff --git a/packages/pipeline/src/crawl.test.ts b/packages/pipeline/src/crawl.test.ts index 37902f3..1610020 100644 --- a/packages/pipeline/src/crawl.test.ts +++ b/packages/pipeline/src/crawl.test.ts @@ -35,7 +35,7 @@ const COMPANY_HTML = `