|
@@ -120,19 +120,34 @@ async function syncProductsToQdrant(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Generate text representations for all products
|
|
// Generate text representations for all products
|
|
|
- const productTexts = validProducts.map((product) =>
|
|
|
|
|
- createProductText({
|
|
|
|
|
- name: product.name,
|
|
|
|
|
- description: product.description,
|
|
|
|
|
- short_description: product.short_description,
|
|
|
|
|
|
|
+ // Extract descriptions, tags, and attributes from ShopRenter's nested structure
|
|
|
|
|
+ const productTexts = validProducts.map((product) => {
|
|
|
|
|
+ // Extract first language description from productDescriptions array
|
|
|
|
|
+ const productDesc = product.productDescriptions?.[0] || {}
|
|
|
|
|
+
|
|
|
|
|
+ // Extract tags from productTags array (ShopRenter structure: [{name: "tag1"}, ...])
|
|
|
|
|
+ const tags = (product.productTags || []).map((t: any) => t.name || t).filter(Boolean)
|
|
|
|
|
+
|
|
|
|
|
+ // Extract attributes from productAttributeExtend array
|
|
|
|
|
+ const attributes = (product.productAttributeExtend || []).map((attr: any) => {
|
|
|
|
|
+ if (attr.name && attr.value) {
|
|
|
|
|
+ return { name: attr.name, options: attr.value }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null
|
|
|
|
|
+ }).filter(Boolean)
|
|
|
|
|
+
|
|
|
|
|
+ return createProductText({
|
|
|
|
|
+ name: productDesc.name || product.name,
|
|
|
|
|
+ description: productDesc.description || null,
|
|
|
|
|
+ short_description: productDesc.shortDescription || null,
|
|
|
sku: product.sku,
|
|
sku: product.sku,
|
|
|
categories: product.categories || [],
|
|
categories: product.categories || [],
|
|
|
- tags: product.tags || [],
|
|
|
|
|
- attributes: product.attributes || [],
|
|
|
|
|
|
|
+ tags: tags,
|
|
|
|
|
+ attributes: attributes,
|
|
|
price: product.price,
|
|
price: product.price,
|
|
|
- meta_description: product.meta_description,
|
|
|
|
|
|
|
+ meta_description: productDesc.metaDescription || null,
|
|
|
})
|
|
})
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
// Generate embeddings in batch
|
|
// Generate embeddings in batch
|
|
|
console.log(`[Qdrant] Generating embeddings for ${productTexts.length} products...`)
|
|
console.log(`[Qdrant] Generating embeddings for ${productTexts.length} products...`)
|
|
@@ -140,64 +155,79 @@ async function syncProductsToQdrant(
|
|
|
console.log(`[Qdrant] Embeddings generated successfully`)
|
|
console.log(`[Qdrant] Embeddings generated successfully`)
|
|
|
|
|
|
|
|
// Convert products to Qdrant points with embeddings and comprehensive details
|
|
// Convert products to Qdrant points with embeddings and comprehensive details
|
|
|
- const points: QdrantPoint[] = validProducts.map((product, index) => ({
|
|
|
|
|
- id: generatePointId('shoprenter', storeId, product.innerId),
|
|
|
|
|
- vector: embeddings[index],
|
|
|
|
|
- payload: {
|
|
|
|
|
- // Basic identification
|
|
|
|
|
- store_id: storeId,
|
|
|
|
|
- product_id: product.innerId.toString(),
|
|
|
|
|
- platform: 'shoprenter',
|
|
|
|
|
- name: product.name,
|
|
|
|
|
- sku: product.sku || null,
|
|
|
|
|
-
|
|
|
|
|
- // Pricing
|
|
|
|
|
- price: parseFloat(product.price) || 0,
|
|
|
|
|
- currency: product.currency || 'HUF',
|
|
|
|
|
- price_gross: product.price_gross ? parseFloat(product.price_gross) : null,
|
|
|
|
|
-
|
|
|
|
|
- // Descriptions
|
|
|
|
|
- description: product.description || null,
|
|
|
|
|
- short_description: product.short_description || null,
|
|
|
|
|
- meta_description: product.meta_description || null,
|
|
|
|
|
-
|
|
|
|
|
- // Categorization
|
|
|
|
|
- categories: product.categories?.map((cat: any) => ({
|
|
|
|
|
- id: cat.id,
|
|
|
|
|
- name: cat.name,
|
|
|
|
|
- slug: cat.slug || null
|
|
|
|
|
- })) || [],
|
|
|
|
|
- tags: product.tags || [],
|
|
|
|
|
-
|
|
|
|
|
- // Attributes
|
|
|
|
|
- attributes: product.attributes || [],
|
|
|
|
|
-
|
|
|
|
|
- // Images
|
|
|
|
|
- images: product.images?.map((img: any) => ({
|
|
|
|
|
- id: img.id,
|
|
|
|
|
- src: img.src || img.url,
|
|
|
|
|
- alt: img.alt || product.name,
|
|
|
|
|
- position: img.position || 0
|
|
|
|
|
- })) || [],
|
|
|
|
|
-
|
|
|
|
|
- // Stock information
|
|
|
|
|
- stock: product.stock || 0,
|
|
|
|
|
- stock_status: product.stock > 0 ? 'instock' : 'outofstock',
|
|
|
|
|
-
|
|
|
|
|
- // Product status
|
|
|
|
|
- active: product.active !== false,
|
|
|
|
|
- status: product.active !== false ? 'active' : 'inactive',
|
|
|
|
|
-
|
|
|
|
|
- // Additional fields
|
|
|
|
|
- manufacturer: product.manufacturer || null,
|
|
|
|
|
- model: product.model || null,
|
|
|
|
|
- weight: product.weight || null,
|
|
|
|
|
- weight_unit: product.weight_unit || 'kg',
|
|
|
|
|
-
|
|
|
|
|
- // Metadata
|
|
|
|
|
- synced_at: new Date().toISOString(),
|
|
|
|
|
|
|
+ const points: QdrantPoint[] = validProducts.map((product, index) => {
|
|
|
|
|
+ // Extract first language description from productDescriptions array
|
|
|
|
|
+ const productDesc = product.productDescriptions?.[0] || {}
|
|
|
|
|
+
|
|
|
|
|
+ // Extract tags from productTags array
|
|
|
|
|
+ const tags = (product.productTags || []).map((t: any) => t.name || t).filter(Boolean)
|
|
|
|
|
+
|
|
|
|
|
+ // Extract attributes from productAttributeExtend array
|
|
|
|
|
+ const attributes = (product.productAttributeExtend || []).map((attr: any) => {
|
|
|
|
|
+ if (attr.name && attr.value) {
|
|
|
|
|
+ return { name: attr.name, value: attr.value }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null
|
|
|
|
|
+ }).filter(Boolean)
|
|
|
|
|
+
|
|
|
|
|
+ // Extract categories from productCategoryRelations (filter out href/URL fields)
|
|
|
|
|
+ const categories = (product.productCategoryRelations || []).map((rel: any) => ({
|
|
|
|
|
+ id: rel.category?.id || null,
|
|
|
|
|
+ })).filter((cat: any) => cat.id)
|
|
|
|
|
+
|
|
|
|
|
+ // Extract manufacturer info (filter out href)
|
|
|
|
|
+ const manufacturer = product.manufacturer?.name || null
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: generatePointId('shoprenter', storeId, product.innerId),
|
|
|
|
|
+ vector: embeddings[index],
|
|
|
|
|
+ payload: {
|
|
|
|
|
+ // Basic identification
|
|
|
|
|
+ store_id: storeId,
|
|
|
|
|
+ product_id: product.innerId.toString(),
|
|
|
|
|
+ platform: 'shoprenter',
|
|
|
|
|
+ name: productDesc.name || product.name || null,
|
|
|
|
|
+ sku: product.sku || null,
|
|
|
|
|
+
|
|
|
|
|
+ // Pricing
|
|
|
|
|
+ price: parseFloat(product.price) || 0,
|
|
|
|
|
+ currency: 'HUF',
|
|
|
|
|
+
|
|
|
|
|
+ // Descriptions (extracted from productDescriptions array)
|
|
|
|
|
+ description: productDesc.description || null,
|
|
|
|
|
+ short_description: productDesc.shortDescription || null,
|
|
|
|
|
+ meta_description: productDesc.metaDescription || null,
|
|
|
|
|
+
|
|
|
|
|
+ // Categorization
|
|
|
|
|
+ categories: categories,
|
|
|
|
|
+ tags: tags,
|
|
|
|
|
+
|
|
|
|
|
+ // Attributes
|
|
|
|
|
+ attributes: attributes,
|
|
|
|
|
+
|
|
|
|
|
+ // Images (filter out hrefs, keep only actual image data)
|
|
|
|
|
+ main_image: product.allImages?.mainImage || null,
|
|
|
|
|
+ image_alt: product.imageAlt || null,
|
|
|
|
|
+
|
|
|
|
|
+ // Stock information
|
|
|
|
|
+ stock: parseInt(product.stock1 || '0') || 0,
|
|
|
|
|
+ stock_status: parseInt(product.stock1 || '0') > 0 ? 'instock' : 'outofstock',
|
|
|
|
|
+
|
|
|
|
|
+ // Product status
|
|
|
|
|
+ active: product.status === '1',
|
|
|
|
|
+ status: product.status === '1' ? 'active' : 'inactive',
|
|
|
|
|
+
|
|
|
|
|
+ // Additional fields
|
|
|
|
|
+ manufacturer: manufacturer,
|
|
|
|
|
+ model: product.modelNumber || null,
|
|
|
|
|
+ weight: product.weight ? parseFloat(product.weight) : null,
|
|
|
|
|
+
|
|
|
|
|
+ // Metadata
|
|
|
|
|
+ synced_at: new Date().toISOString(),
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }))
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
|
|
|
await upsertPoints(collectionName, points)
|
|
await upsertPoints(collectionName, points)
|
|
|
synced = points.length
|
|
synced = points.length
|