fix: enrichment pipeline reliability + loading page gradient + button click area

- generate-report: filter empty strings from AI social handles, add saveError logging
- useReport: 3-level fallback for social handles (report > clinicInfo > scrape_data)
- useEnrichment: always trigger enrichment if clinicName exists (not just IG/YT handles)
- Hero: pointer-events-none on decorative blobs (were blocking button clicks)
- AnalysisLoadingPage: warm gradient on INFINITH logo text (#fff3eb → #e4cfff → #f5f9ff)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
claude/bold-hawking
Haewon Kam 2026-04-03 16:05:33 +09:00
parent 72ea8f4a2d
commit ad625e08ee
5 changed files with 43 additions and 21 deletions

View File

@ -79,9 +79,9 @@ export default function Hero() {
</div>
{/* Decorative elements */}
<div className="absolute top-1/4 left-10 w-64 h-64 bg-purple-300 rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob"></div>
<div className="absolute top-1/3 right-10 w-64 h-64 bg-pink-300 rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob animation-delay-2000"></div>
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 w-64 h-64 bg-indigo-300 rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob animation-delay-4000"></div>
<div className="absolute top-1/4 left-10 w-64 h-64 bg-purple-300 rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob pointer-events-none"></div>
<div className="absolute top-1/3 right-10 w-64 h-64 bg-pink-300 rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob animation-delay-2000 pointer-events-none"></div>
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 w-64 h-64 bg-indigo-300 rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-blob animation-delay-4000 pointer-events-none"></div>
</section>
);
}

View File

@ -34,8 +34,7 @@ export function useEnrichment(
useEffect(() => {
if (!baseReport || !params?.reportId || hasTriggered.current) return;
// Don't enrich if no social handles are available
if (!params.instagramHandle && !params.instagramHandles?.length && !params.youtubeChannelId) return;
// Always enrich if clinicName exists — Naver, 강남언니, Google Maps work with name alone
hasTriggered.current = true;
setStatus('loading');

View File

@ -76,21 +76,30 @@ export function useReport(id: string | undefined): UseReportResult {
},
);
// Recover social handles: report.socialHandles > scrape_data.clinic.socialMedia
let handles = (reportJson.socialHandles as Record<string, string | null>) || null;
if (!handles && scrapeData) {
const clinic = scrapeData.clinic as Record<string, unknown> | undefined;
const socialMedia = clinic?.socialMedia as Record<string, string> | undefined;
if (socialMedia) {
// Recover social handles: report.socialHandles > AI clinicInfo.socialMedia > scrape_data
let handles = (reportJson.socialHandles as Record<string, string | null | string[]>) || null;
if (!handles) {
// Try AI-generated socialMedia in clinicInfo
const aiSocial = (reportJson.clinicInfo as Record<string, unknown>)?.socialMedia as Record<string, unknown> | undefined;
const scrapeSocial = scrapeData
? (scrapeData.clinic as Record<string, unknown>)?.socialMedia as Record<string, string> | undefined
: undefined;
const igSource = aiSocial?.instagramAccounts || aiSocial?.instagram || scrapeSocial?.instagram;
const ytSource = (aiSocial?.youtube as string) || scrapeSocial?.youtube;
if (igSource || ytSource) {
handles = {
instagram: normalizeInstagramHandle(socialMedia.instagram),
youtube: socialMedia.youtube || null,
facebook: socialMedia.facebook || null,
blog: socialMedia.blog || null,
instagram: Array.isArray(igSource)
? igSource.map((h: string) => normalizeInstagramHandle(h)).filter(Boolean)
: normalizeInstagramHandle(igSource as string),
youtube: ytSource || null,
facebook: (aiSocial?.facebook as string) || scrapeSocial?.facebook || null,
blog: (aiSocial?.naverBlog as string) || scrapeSocial?.blog || null,
};
}
}
setSocialHandles(handles);
setSocialHandles(handles as Record<string, string | null> | null);
// If channelEnrichment already exists in DB, merge it immediately
const enrichment = reportJson.channelEnrichment as EnrichmentData | undefined;

View File

@ -81,7 +81,12 @@ export default function AnalysisLoadingPage() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-4xl md:text-5xl font-serif font-bold text-gradient mb-4"
className="text-4xl md:text-5xl font-serif font-bold mb-4"
style={{
background: 'linear-gradient(to right, #fff3eb, #e4cfff, #f5f9ff)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
INFINITH
</motion.h1>

View File

@ -187,11 +187,15 @@ ${JSON.stringify(analyzeResult.data?.analysis || {}, null, 2)}
.filter((h): h is string => h !== null)
)];
// Filter out empty strings — AI sometimes returns "" instead of null
const pickNonEmpty = (...vals: (string | null | undefined)[]): string | null =>
vals.find(v => v && v.trim().length > 0) || null;
const normalizedHandles = {
instagram: igHandles.length > 0 ? igHandles : null, // array of handles
youtube: aiSocial.youtube || scrapeSocial.youtube || null,
facebook: aiSocial.facebook || scrapeSocial.facebook || null,
blog: aiSocial.naverBlog || scrapeSocial.blog || null,
instagram: igHandles.length > 0 ? igHandles : null,
youtube: pickNonEmpty(aiSocial.youtube, scrapeSocial.youtube),
facebook: pickNonEmpty(aiSocial.facebook, scrapeSocial.facebook),
blog: pickNonEmpty(aiSocial.naverBlog, scrapeSocial.blog),
};
// Embed normalized handles in report for DB persistence
@ -211,6 +215,10 @@ ${JSON.stringify(analyzeResult.data?.analysis || {}, null, 2)}
.select("id")
.single();
if (saveError) {
console.error("DB save error:", saveError);
}
return new Response(
JSON.stringify({
success: true,
@ -226,6 +234,7 @@ ${JSON.stringify(analyzeResult.data?.analysis || {}, null, 2)}
aiGeneration: !report.parseError,
},
socialHandles: normalizedHandles,
saveError: saveError?.message || null,
address,
services,
},