This commit is contained in:
sebseb7
2025-11-17 08:43:57 +01:00
parent 521cc307a3
commit 11ba2db893
5 changed files with 173 additions and 30 deletions

View File

@@ -51,17 +51,35 @@ function readListFile(filePath) {
throw new Error('File is empty'); throw new Error('File is empty');
} }
// Parse first line: categoryName,categoryId // Parse first line: categoryName,categoryId,[subcategoryIds]
const firstLine = lines[0]; const firstLine = lines[0];
const [categoryName, categoryId] = firstLine.split(','); const parts = firstLine.split(',');
if (parts.length < 2) {
throw new Error('Invalid first line format');
}
const categoryName = parts[0].replace(/^"|"$/g, '');
const categoryId = parts[1].replace(/^"|"$/g, '');
// Parse subcategory IDs from array notation [id1,id2,...]
let subcategoryIds = [];
if (parts.length >= 3) {
const subcatString = parts.slice(2).join(','); // Handle case where array spans multiple comma-separated values
const match = subcatString.match(/\[(.*?)\]/);
if (match && match[1]) {
subcategoryIds = match[1].split(',').map(id => id.trim()).filter(id => id);
}
}
if (!categoryName || !categoryId) { if (!categoryName || !categoryId) {
throw new Error('Invalid first line format'); throw new Error('Invalid first line format');
} }
return { return {
categoryName: categoryName.replace(/^"|"$/g, ''), // Remove quotes if present categoryName: categoryName,
categoryId: categoryId.replace(/^"|"$/g, ''), categoryId: categoryId,
subcategoryIds: subcategoryIds,
content: content content: content
}; };
} catch (error) { } catch (error) {
@@ -70,11 +88,66 @@ function readListFile(filePath) {
} }
} }
// Function to build processing order based on dependencies
function buildProcessingOrder(categories) {
const categoryMap = new Map();
const processed = new Set();
const processingOrder = [];
// Create a map of categoryId -> category data
categories.forEach(cat => {
categoryMap.set(cat.categoryId, cat);
});
// Function to check if all subcategories are processed
function canProcess(category) {
return category.subcategoryIds.every(subId => processed.has(subId));
}
// Keep processing until all categories are done
while (processingOrder.length < categories.length) {
const beforeLength = processingOrder.length;
// Find categories that can be processed now
for (const category of categories) {
if (!processed.has(category.categoryId) && canProcess(category)) {
processingOrder.push(category);
processed.add(category.categoryId);
}
}
// If no progress was made, there might be a circular dependency or missing category
if (processingOrder.length === beforeLength) {
console.error('⚠️ Unable to resolve all category dependencies');
// Add remaining categories anyway
for (const category of categories) {
if (!processed.has(category.categoryId)) {
console.warn(` Adding ${category.categoryName} (${category.categoryId}) despite unresolved dependencies`);
processingOrder.push(category);
processed.add(category.categoryId);
}
}
break;
}
}
return processingOrder;
}
// Function to generate SEO description using OpenAI // Function to generate SEO description using OpenAI
async function generateSEODescription(productListContent, categoryName, categoryId) { async function generateSEODescription(productListContent, categoryName, categoryId, subcategoryDescriptions = []) {
try { try {
console.log(`🔄 Generating SEO description for category: ${categoryName} (ID: ${categoryId})`); console.log(`🔄 Generating SEO description for category: ${categoryName} (ID: ${categoryId})`);
// Prepend subcategory information if present
let fullContent = productListContent;
if (subcategoryDescriptions.length > 0) {
const subcatInfo = 'This category has the following subcategories:\n' +
subcategoryDescriptions.map(sub => `- "${sub.name}": ${sub.description}`).join('\n') +
'\n\n';
fullContent = subcatInfo + productListContent;
}
const response = await openai.responses.create({ const response = await openai.responses.create({
model: "gpt-5.1", model: "gpt-5.1",
input: [ input: [
@@ -92,7 +165,7 @@ async function generateSEODescription(productListContent, categoryName, category
"content": [ "content": [
{ {
"type": "input_text", "type": "input_text",
"text": productListContent "text": fullContent
} }
] ]
} }
@@ -124,15 +197,8 @@ async function generateSEODescription(productListContent, categoryName, category
"verbosity": "medium" "verbosity": "medium"
}, },
reasoning: { reasoning: {
"effort": "none", "effort": "none"
"summary": "auto" }
},
tools: [],
store: false,
include: [
"reasoning.encrypted_content",
"web_search_call.action.sources"
]
}); });
const description = response.output_text; const description = response.output_text;
@@ -190,31 +256,97 @@ async function main() {
console.log(`📂 Found ${listFiles.length} list files to process`); console.log(`📂 Found ${listFiles.length} list files to process`);
const results = []; // Step 1: Read all list files and extract category information
console.log('📖 Reading all category files...');
const categories = [];
const fileDataMap = new Map(); // Map categoryId -> fileData
// Process each list file
for (const listFile of listFiles) { for (const listFile of listFiles) {
const filePath = path.join(DIST_DIR, listFile); const filePath = path.join(DIST_DIR, listFile);
// Read and parse the file
const fileData = readListFile(filePath); const fileData = readListFile(filePath);
if (!fileData) { if (!fileData) {
console.log(`⚠️ Skipping ${listFile} due to read error`); console.log(`⚠️ Skipping ${listFile} due to read error`);
continue; continue;
} }
categories.push({
categoryId: fileData.categoryId,
categoryName: fileData.categoryName,
subcategoryIds: fileData.subcategoryIds,
listFileName: listFile
});
fileDataMap.set(fileData.categoryId, {
...fileData,
listFileName: listFile
});
}
console.log(`✅ Read ${categories.length} categories`);
// Step 2: Build processing order based on dependencies
console.log('🔨 Building processing order based on category hierarchy...');
const processingOrder = buildProcessingOrder(categories);
const leafCategories = processingOrder.filter(cat => cat.subcategoryIds.length === 0);
const parentCategories = processingOrder.filter(cat => cat.subcategoryIds.length > 0);
console.log(` 📄 ${leafCategories.length} leaf categories (no subcategories)`);
console.log(` 📁 ${parentCategories.length} parent categories (with subcategories)`);
// Step 3: Process categories in order
const results = [];
const generatedDescriptions = new Map(); // Map categoryId -> {seo_description, long_description}
for (const category of processingOrder) {
const fileData = fileDataMap.get(category.categoryId);
if (!fileData) {
console.log(`⚠️ Skipping ${category.categoryName} - no file data found`);
continue;
}
// Gather subcategory descriptions
const subcategoryDescriptions = [];
for (const subId of category.subcategoryIds) {
const subDesc = generatedDescriptions.get(subId);
const subCategory = categories.find(cat => cat.categoryId === subId);
if (subDesc && subCategory) {
subcategoryDescriptions.push({
name: subCategory.categoryName,
description: subDesc.long_description || subDesc.seo_description
});
} else if (subCategory) {
console.warn(` ⚠️ Subcategory ${subCategory.categoryName} (${subId}) not yet processed`);
}
}
// Generate SEO description // Generate SEO description
const description = await generateSEODescription( const descriptionJSON = await generateSEODescription(
fileData.content, fileData.content,
fileData.categoryName, fileData.categoryName,
fileData.categoryId fileData.categoryId,
subcategoryDescriptions
); );
// Parse the JSON response
let parsedDescription;
try {
parsedDescription = JSON.parse(descriptionJSON);
generatedDescriptions.set(category.categoryId, parsedDescription);
} catch (error) {
console.error(` ❌ Failed to parse JSON for ${category.categoryName}:`, error.message);
parsedDescription = { seo_description: descriptionJSON, long_description: descriptionJSON };
generatedDescriptions.set(category.categoryId, parsedDescription);
}
// Store result // Store result
results.push({ results.push({
categoryId: fileData.categoryId, categoryId: category.categoryId,
listFileName: listFile, listFileName: fileData.listFileName,
description: description description: parsedDescription.seo_description || descriptionJSON
}); });
// Add delay to avoid rate limiting // Add delay to avoid rate limiting
@@ -242,6 +374,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
export { export {
findListFiles, findListFiles,
readListFile, readListFile,
buildProcessingOrder,
generateSEODescription, generateSEODescription,
writeCSV writeCSV
}; };

View File

@@ -247,10 +247,6 @@ const renderPage = (
if (!suppressLogs) { if (!suppressLogs) {
console.log(`${description} prerendered to ${outputPath}`); console.log(`${description} prerendered to ${outputPath}`);
console.log(` - Markup length: ${renderedMarkup.length} characters`); console.log(` - Markup length: ${renderedMarkup.length} characters`);
console.log(` - CSS rules: ${Object.keys(cache.inserted).length}`);
console.log(` - Total inlined CSS: ${Math.round(combinedCss.length / 1024)}KB`);
console.log(` - Render-blocking CSS eliminated: ${inlinedCss ? 'YES' : 'NO'}`);
console.log(` - Fallback content saved to window.__PRERENDER_FALLBACK__`);
if (productDetailCacheScript) { if (productDetailCacheScript) {
console.log(` - Product detail cache populated for SPA hydration`); console.log(` - Product detail cache populated for SPA hydration`);
} }

View File

@@ -122,6 +122,7 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
689: "543561", // Seeds (Saatgut) 689: "543561", // Seeds (Saatgut)
706: "543561", // Stecklinge (cuttings) ebenfalls Pflanzen/Saatgut 706: "543561", // Stecklinge (cuttings) ebenfalls Pflanzen/Saatgut
376: "2802", // Grow-Sets Pflanzen- & Kräuteranbausets 376: "2802", // Grow-Sets Pflanzen- & Kräuteranbausets
915: "2802", // Grow-Sets > Set-Zubehör Pflanzen- & Kräuteranbausets
// Headshop & Accessories // Headshop & Accessories
709: "4082", // Headshop Rauchzubehör 709: "4082", // Headshop Rauchzubehör
@@ -129,8 +130,13 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
714: "4082", // Headshop > Bongs > Zubehör Rauchzubehör 714: "4082", // Headshop > Bongs > Zubehör Rauchzubehör
748: "4082", // Headshop > Bongs > Köpfe Rauchzubehör 748: "4082", // Headshop > Bongs > Köpfe Rauchzubehör
749: "4082", // Headshop > Bongs > Chillums/Diffusoren/Kupplungen Rauchzubehör 749: "4082", // Headshop > Bongs > Chillums/Diffusoren/Kupplungen Rauchzubehör
921: "4082", // Headshop > Pfeifen Rauchzubehör
924: "4082", // Headshop > Dabbing Rauchzubehör
896: "3151", // Headshop > Vaporizer Vaporizer 896: "3151", // Headshop > Vaporizer Vaporizer
923: "4082", // Headshop > Papes & Blunts Rauchzubehör
710: "5109", // Headshop > Grinder Gewürzmühlen (Küchenhelfer) 710: "5109", // Headshop > Grinder Gewürzmühlen (Küchenhelfer)
922: "4082", // Headshop > Aktivkohlefilter & Tips Rauchzubehör
916: "4082", // Headshop > Rollen & Bauen Rauchzubehör
// Measuring & Packaging // Measuring & Packaging
186: "5631", // Headshop > Wiegen & Verpacken Aufbewahrung/Zubehör 186: "5631", // Headshop > Wiegen & Verpacken Aufbewahrung/Zubehör
@@ -140,6 +146,7 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
407: "3561", // Headshop > Grove Bags Aufbewahrungsbehälter 407: "3561", // Headshop > Grove Bags Aufbewahrungsbehälter
449: "1496", // Headshop > Cliptütchen Lebensmittelverpackungsmaterial 449: "1496", // Headshop > Cliptütchen Lebensmittelverpackungsmaterial
539: "3110", // Headshop > Gläser & Dosen Lebensmittelbehälter 539: "3110", // Headshop > Gläser & Dosen Lebensmittelbehälter
920: "581", // Headshop > Räucherstäbchen Raumdüfte (Home Fragrances)
// Lighting & Equipment // Lighting & Equipment
694: "3006", // Lampen Lampen (Beleuchtung) 694: "3006", // Lampen Lampen (Beleuchtung)

View File

@@ -259,7 +259,8 @@ const generateCategoryProductList = (category, categoryProducts = []) => {
const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-'); const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-');
const fileName = `llms-${categorySlug}-list.txt`; const fileName = `llms-${categorySlug}-list.txt`;
let content = `${String(category.name)},${String(category.id)}\n`; const subcategoryIds = (category.subcategories || []).join(',');
let content = `${String(category.name)},${String(category.id)},[${subcategoryIds}]\n`;
categoryProducts.forEach((product) => { categoryProducts.forEach((product) => {
const artnr = String(product.articleNumber || ''); const artnr = String(product.articleNumber || '');

View File

@@ -7,11 +7,17 @@ const collectAllCategories = (categoryNode, categories = []) => {
// Add current category (skip root category 209) // Add current category (skip root category 209)
if (categoryNode.id !== 209) { if (categoryNode.id !== 209) {
// Extract subcategory IDs from children
const subcategoryIds = categoryNode.children
? categoryNode.children.map(child => child.id)
: [];
categories.push({ categories.push({
id: categoryNode.id, id: categoryNode.id,
name: categoryNode.name, name: categoryNode.name,
seoName: categoryNode.seoName, seoName: categoryNode.seoName,
parentId: categoryNode.parentId parentId: categoryNode.parentId,
subcategories: subcategoryIds
}); });
} }