The two AI tool cards rendered inside the chat — CaseStudyViewer (the
"Show me proven installations" card) and EquipmentConfigurator (the
"Show equipment specs" card) — were composing image URLs without the
node-slug segment:
/cases/<filename> ← what the cards were emitting (404)
/cases/<nodeSlug>/<filename> ← what the public site actually serves
Result: cover images and gallery thumbnails inside chat cards came back
as broken-image icons, while the same files rendered fine on
/en/applications/<slug> and inside the CaseStudyModal (those
already used the correct path).
Fix: both components now derive the slug from data.title with the same
nodeToSlug() the public pages use, and prefix it on every /cases/
URL — cover and gallery thumbnails alike.
CHANGES (2 files)
- src/components/ai/CaseStudyViewer.tsx
- Added local nodeToSlug() helper (mirrors ApplicationClient + assetFolders)
- coverSrc: /cases/${nodeSlug}/${mediaFileName}
- gallery image: /cases/${nodeSlug}/${img}
- src/components/ai/EquipmentConfigurator.tsx
- Added local nodeToSlug() helper
- coverSrc: /cases/${nodeSlug}/${mediaFileName}
No backend / API / DB changes. Pure client-side path correction.
This commit is contained in:
@@ -5,6 +5,13 @@ import { MapPin, Factory, Zap, Clock, ChevronDown, ArrowRight, Globe2, Image as
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
// Slugify the node title the same way ApplicationClient + assetFolders do —
|
||||
// keeps image paths consistent with the on-disk layout
|
||||
// (/cases/<nodeSlug>/<file>).
|
||||
function nodeToSlug(title: string): string {
|
||||
return title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
// ── Interface matches GlobalNode shape from Prisma ──
|
||||
interface CaseStudyData {
|
||||
found: boolean;
|
||||
@@ -50,7 +57,8 @@ export default function CaseStudyViewer({ data }: { data: CaseStudyData }) {
|
||||
if (!data.found) return null;
|
||||
|
||||
const accent = ACCENTS[data.industry] || ACCENTS.textile;
|
||||
const coverSrc = data.mediaFileName ? `/cases/${data.mediaFileName}` : null;
|
||||
const nodeSlug = nodeToSlug(data.title);
|
||||
const coverSrc = data.mediaFileName ? `/cases/${nodeSlug}/${data.mediaFileName}` : null;
|
||||
const appLabel = data.application.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase());
|
||||
|
||||
return (
|
||||
@@ -192,7 +200,7 @@ export default function CaseStudyViewer({ data }: { data: CaseStudyData }) {
|
||||
<div className="grid grid-cols-3 gap-1.5 rounded-lg overflow-hidden">
|
||||
{data.gallery.slice(0, 6).map((img, i) => (
|
||||
<div key={i} className="relative aspect-square bg-[#F5F5F7] dark:bg-[#1D1D1F] rounded-md overflow-hidden">
|
||||
<Image src={`/cases/${img}`} alt={`Gallery ${i + 1}`} fill className="object-cover" sizes="120px" />
|
||||
<Image src={`/cases/${nodeSlug}/${img}`} alt={`Gallery ${i + 1}`} fill className="object-cover" sizes="120px" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,12 @@ import { Cpu, ArrowRight, Settings2, ChevronDown, MapPin, Factory, Zap, Box } fr
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
// Same slugger as the public-facing pages so cover images resolve to the
|
||||
// /cases/<nodeSlug>/<file> layout that's actually on disk.
|
||||
function nodeToSlug(title: string): string {
|
||||
return title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
// ── Interface matches GlobalNode + specificDatasheetJson from Prisma ──
|
||||
interface EquipmentData {
|
||||
found: boolean;
|
||||
@@ -39,7 +45,8 @@ export default function EquipmentConfigurator({ data }: { data: EquipmentData })
|
||||
|
||||
if (!data.found) return null;
|
||||
|
||||
const coverSrc = data.mediaFileName ? `/cases/${data.mediaFileName}` : null;
|
||||
const nodeSlug = nodeToSlug(data.title);
|
||||
const coverSrc = data.mediaFileName ? `/cases/${nodeSlug}/${data.mediaFileName}` : null;
|
||||
const appLabel = data.application.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase());
|
||||
|
||||
// Find key specs for header pills (power, frequency, model — from datasheet)
|
||||
|
||||
Reference in New Issue
Block a user