vantuz 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +44 -0
- package/cli.js +420 -0
- package/core/ai-analyst.js +46 -0
- package/core/database.js +74 -0
- package/core/license-manager.js +50 -0
- package/core/product-manager.js +134 -0
- package/package.json +72 -0
- package/plugins/vantuz/index.js +480 -0
- package/plugins/vantuz/memory/hippocampus.js +464 -0
- package/plugins/vantuz/package.json +21 -0
- package/plugins/vantuz/platforms/amazon.js +239 -0
- package/plugins/vantuz/platforms/ciceksepeti.js +175 -0
- package/plugins/vantuz/platforms/hepsiburada.js +189 -0
- package/plugins/vantuz/platforms/index.js +215 -0
- package/plugins/vantuz/platforms/n11.js +263 -0
- package/plugins/vantuz/platforms/pazarama.js +171 -0
- package/plugins/vantuz/platforms/pttavm.js +136 -0
- package/plugins/vantuz/platforms/trendyol.js +307 -0
- package/plugins/vantuz/services/alerts.js +253 -0
- package/plugins/vantuz/services/license.js +237 -0
- package/plugins/vantuz/services/scheduler.js +232 -0
- package/plugins/vantuz/tools/analytics.js +152 -0
- package/plugins/vantuz/tools/crossborder.js +187 -0
- package/plugins/vantuz/tools/nl-parser.js +211 -0
- package/plugins/vantuz/tools/product.js +110 -0
- package/plugins/vantuz/tools/quick-report.js +175 -0
- package/plugins/vantuz/tools/repricer.js +314 -0
- package/plugins/vantuz/tools/sentiment.js +115 -0
- package/plugins/vantuz/tools/vision.js +257 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🩸 KAN EMİCİ (Blood Sucker) - Akıllı Fiyat Robotu
|
|
3
|
+
*
|
|
4
|
+
* Rakip fiyatlarını 7/24 izler ama aptal değil:
|
|
5
|
+
* - Rakip fiyat düşürdüyse körü körüne takip etmez
|
|
6
|
+
* - Stok durumuna, kar marjına ve satış hızına bakar
|
|
7
|
+
* - Rakip stoku bitiyorsa fiyatı yükseltir
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import platformHub from '../platforms/index.js';
|
|
11
|
+
|
|
12
|
+
export const repricerTool = {
|
|
13
|
+
name: 'repricer',
|
|
14
|
+
|
|
15
|
+
async execute(params, context) {
|
|
16
|
+
const { api, memory, license } = context;
|
|
17
|
+
const { barcode, platform = 'all', targetMargin = 20, action = 'analyze' } = params;
|
|
18
|
+
|
|
19
|
+
// Lisans kontrolü (demo modda da çalışsın)
|
|
20
|
+
const isDemo = license?.isDemo?.() ?? true;
|
|
21
|
+
if (isDemo) {
|
|
22
|
+
api?.logger?.info('🔓 Demo modda çalışıyor...');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Ürün bilgilerini al
|
|
26
|
+
const product = await this._getProduct(barcode, platform);
|
|
27
|
+
if (!product) {
|
|
28
|
+
return { success: false, error: 'Ürün bulunamadı.' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Hafızadan geçmiş bağlamı al
|
|
32
|
+
let historyContext = { recentDecisions: [], productHistory: [] };
|
|
33
|
+
if (memory?.getRelevantContext) {
|
|
34
|
+
historyContext = await memory.getRelevantContext(barcode, {
|
|
35
|
+
barcode,
|
|
36
|
+
type: 'decision'
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Rakip fiyatlarını topla
|
|
41
|
+
const competitors = await this._fetchCompetitorPrices(barcode, platform);
|
|
42
|
+
|
|
43
|
+
// Analiz yap
|
|
44
|
+
const analysis = this._analyzeAndDecide({
|
|
45
|
+
product,
|
|
46
|
+
competitors,
|
|
47
|
+
targetMargin,
|
|
48
|
+
history: historyContext.recentDecisions,
|
|
49
|
+
productContext: historyContext.productHistory
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Kararı hafızaya kaydet
|
|
53
|
+
if (memory?.recordPricingDecision) {
|
|
54
|
+
await memory.recordPricingDecision({
|
|
55
|
+
productId: product.id,
|
|
56
|
+
barcode,
|
|
57
|
+
platform,
|
|
58
|
+
previousPrice: product.price,
|
|
59
|
+
newPrice: analysis.recommendedPrice,
|
|
60
|
+
reason: analysis.reason,
|
|
61
|
+
factors: analysis.factors,
|
|
62
|
+
outcome: action === 'apply' ? 'applied' : 'pending',
|
|
63
|
+
profitImpact: analysis.profitImpact
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Eğer action = apply ise fiyatı güncelle
|
|
68
|
+
if (action === 'apply' && analysis.shouldChange && !isDemo) {
|
|
69
|
+
await this._applyPrice(product, analysis.recommendedPrice, platform);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
product: {
|
|
75
|
+
name: product.name,
|
|
76
|
+
barcode,
|
|
77
|
+
currentPrice: product.price,
|
|
78
|
+
cost: product.cost
|
|
79
|
+
},
|
|
80
|
+
analysis: {
|
|
81
|
+
recommendedPrice: analysis.recommendedPrice,
|
|
82
|
+
reason: analysis.reason,
|
|
83
|
+
shouldChange: analysis.shouldChange,
|
|
84
|
+
profitImpact: analysis.profitImpact,
|
|
85
|
+
competitorSummary: analysis.competitorSummary
|
|
86
|
+
},
|
|
87
|
+
applied: action === 'apply' && analysis.shouldChange && !isDemo
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
_analyzeAndDecide({ product, competitors, targetMargin, history, productContext }) {
|
|
92
|
+
const { price: currentPrice, cost } = product;
|
|
93
|
+
const minPrice = cost * (1 + targetMargin / 100);
|
|
94
|
+
|
|
95
|
+
// Rakip analizi
|
|
96
|
+
const activeCompetitors = competitors.filter(c => c.stock > 0);
|
|
97
|
+
const lowestCompetitorPrice = activeCompetitors.length > 0
|
|
98
|
+
? Math.min(...activeCompetitors.map(c => c.price))
|
|
99
|
+
: null;
|
|
100
|
+
|
|
101
|
+
// Rakip stok durumu
|
|
102
|
+
const lowStockCompetitors = competitors.filter(c => c.stock > 0 && c.stock < 5);
|
|
103
|
+
const outOfStockCompetitors = competitors.filter(c => c.stock === 0);
|
|
104
|
+
|
|
105
|
+
let recommendedPrice = currentPrice;
|
|
106
|
+
let reason = '';
|
|
107
|
+
let shouldChange = false;
|
|
108
|
+
|
|
109
|
+
// KARAR MANTIĞI
|
|
110
|
+
|
|
111
|
+
// Senaryo 1: Rakiplerin çoğu stoksuz
|
|
112
|
+
if (outOfStockCompetitors.length >= competitors.length * 0.6 && competitors.length > 0) {
|
|
113
|
+
recommendedPrice = Math.round(currentPrice * 1.15);
|
|
114
|
+
reason = `🔥 Rakiplerin %${Math.round(outOfStockCompetitors.length / competitors.length * 100)}'i stoksuz. Fiyat artışı önerilir.`;
|
|
115
|
+
shouldChange = true;
|
|
116
|
+
}
|
|
117
|
+
// Senaryo 2: En yakın rakip düşük stoklu
|
|
118
|
+
else if (lowStockCompetitors.length > 0 && lowestCompetitorPrice) {
|
|
119
|
+
const lowestStockCompetitor = lowStockCompetitors.sort((a, b) => a.price - b.price)[0];
|
|
120
|
+
if (lowestStockCompetitor && lowestStockCompetitor.price <= currentPrice) {
|
|
121
|
+
recommendedPrice = Math.round(currentPrice * 1.05);
|
|
122
|
+
reason = `⏳ Rakip fiyatı düşük ama stoku kritik (${lowestStockCompetitor.stock} adet). Bekleyince müşteri bize gelecek.`;
|
|
123
|
+
shouldChange = currentPrice < recommendedPrice;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Senaryo 3: Rakip fiyatı bizden düşük ve stoku bol
|
|
127
|
+
else if (lowestCompetitorPrice && lowestCompetitorPrice < currentPrice * 0.95) {
|
|
128
|
+
if (lowestCompetitorPrice >= minPrice) {
|
|
129
|
+
recommendedPrice = Math.round(lowestCompetitorPrice * 0.99);
|
|
130
|
+
reason = `📉 Rakip fiyatı düşürmüş (${lowestCompetitorPrice} ₺). Kar marjı uygun, takip ediyoruz.`;
|
|
131
|
+
shouldChange = true;
|
|
132
|
+
} else {
|
|
133
|
+
recommendedPrice = minPrice;
|
|
134
|
+
reason = `⚠️ Rakip fiyatı çok düşük (${lowestCompetitorPrice} ₺) ama minimum marjın (${targetMargin}%) altına inemeyiz.`;
|
|
135
|
+
shouldChange = currentPrice > minPrice;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Senaryo 4: Fiyatımız çok düşük, artırabiliriz
|
|
139
|
+
else if (lowestCompetitorPrice && currentPrice < lowestCompetitorPrice * 0.9) {
|
|
140
|
+
recommendedPrice = Math.round(lowestCompetitorPrice * 0.95);
|
|
141
|
+
reason = `📈 Fiyatımız gereksiz düşük. Rakip fiyatına yaklaştırılıyor.`;
|
|
142
|
+
shouldChange = true;
|
|
143
|
+
}
|
|
144
|
+
// Senaryo 5: Stabil piyasa veya rakip yok
|
|
145
|
+
else {
|
|
146
|
+
reason = competitors.length > 0
|
|
147
|
+
? `✅ Fiyat optimal seviyede. Değişiklik gerekmiyor.`
|
|
148
|
+
: `ℹ️ Rakip verisi bulunamadı.`;
|
|
149
|
+
shouldChange = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Kar etkisi hesapla
|
|
153
|
+
const currentProfit = currentPrice - cost;
|
|
154
|
+
const newProfit = recommendedPrice - cost;
|
|
155
|
+
const profitImpact = currentProfit > 0
|
|
156
|
+
? ((newProfit - currentProfit) / currentProfit * 100).toFixed(1)
|
|
157
|
+
: 0;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
recommendedPrice,
|
|
161
|
+
reason,
|
|
162
|
+
shouldChange,
|
|
163
|
+
profitImpact: parseFloat(profitImpact),
|
|
164
|
+
competitorSummary: {
|
|
165
|
+
total: competitors.length,
|
|
166
|
+
active: activeCompetitors.length,
|
|
167
|
+
lowStock: lowStockCompetitors.length,
|
|
168
|
+
outOfStock: outOfStockCompetitors.length,
|
|
169
|
+
lowestPrice: lowestCompetitorPrice
|
|
170
|
+
},
|
|
171
|
+
factors: {
|
|
172
|
+
currentPrice,
|
|
173
|
+
cost,
|
|
174
|
+
minPrice,
|
|
175
|
+
targetMargin,
|
|
176
|
+
competitorCount: competitors.length,
|
|
177
|
+
lowestCompetitorPrice
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async analyzeCompetitors(barcode, context) {
|
|
183
|
+
const competitors = await this._fetchCompetitorPrices(barcode, 'all');
|
|
184
|
+
const product = await this._getProduct(barcode, 'all');
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
product: product?.name || barcode,
|
|
188
|
+
competitors: competitors.slice(0, 5),
|
|
189
|
+
recommendation: this._generateRecommendation(competitors, product)
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
async runAutoCycle(context) {
|
|
194
|
+
const decisions = [];
|
|
195
|
+
|
|
196
|
+
// Bağlı platformlardan ürünleri al
|
|
197
|
+
const connected = platformHub.getConnected();
|
|
198
|
+
if (connected.length === 0) return decisions;
|
|
199
|
+
|
|
200
|
+
for (const platformName of connected) {
|
|
201
|
+
const api = platformHub.resolve(platformName);
|
|
202
|
+
if (!api) continue;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const result = await api.getProducts({ size: 50 });
|
|
206
|
+
if (!result?.success) continue;
|
|
207
|
+
|
|
208
|
+
const products = result.data.content || result.data.products || result.data || [];
|
|
209
|
+
|
|
210
|
+
for (const product of products.slice(0, 10)) {
|
|
211
|
+
const barcode = product.barcode || product.sku || product.merchantSku;
|
|
212
|
+
if (!barcode) continue;
|
|
213
|
+
|
|
214
|
+
const analysisResult = await this.execute({
|
|
215
|
+
barcode,
|
|
216
|
+
platform: platformName,
|
|
217
|
+
action: 'analyze'
|
|
218
|
+
}, context);
|
|
219
|
+
|
|
220
|
+
if (analysisResult.success && analysisResult.analysis.shouldChange) {
|
|
221
|
+
decisions.push({
|
|
222
|
+
barcode,
|
|
223
|
+
name: product.title || product.name,
|
|
224
|
+
platform: platformName,
|
|
225
|
+
...analysisResult.analysis
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} catch (err) {
|
|
230
|
+
// Hata durumunda devam et
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return decisions;
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
// === Private Methods ===
|
|
238
|
+
|
|
239
|
+
async _getProduct(barcode, platform) {
|
|
240
|
+
// Platformlardan ürün bilgisi al
|
|
241
|
+
const platforms = platform === 'all'
|
|
242
|
+
? platformHub.getConnected()
|
|
243
|
+
: [platform];
|
|
244
|
+
|
|
245
|
+
for (const p of platforms) {
|
|
246
|
+
const api = platformHub.resolve(p);
|
|
247
|
+
if (!api?.getProducts) continue;
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const result = await api.getProducts({ barcode });
|
|
251
|
+
if (result?.success) {
|
|
252
|
+
const products = result.data.content || result.data.products || result.data || [];
|
|
253
|
+
const product = products.find(item =>
|
|
254
|
+
item.barcode === barcode ||
|
|
255
|
+
item.sku === barcode ||
|
|
256
|
+
item.merchantSku === barcode
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
if (product) {
|
|
260
|
+
return {
|
|
261
|
+
id: product.id || `${p}_${barcode}`,
|
|
262
|
+
barcode,
|
|
263
|
+
name: product.title || product.name || 'Ürün',
|
|
264
|
+
price: product.salePrice || product.price || product.salesPrice || 0,
|
|
265
|
+
cost: product.cost || (product.salePrice || product.price || 0) * 0.6, // Maliyet yoksa %60 varsay
|
|
266
|
+
stock: product.quantity || product.stock || product.availableStock || 0,
|
|
267
|
+
platform: p
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch (e) {
|
|
272
|
+
// Hata durumunda diğer platformu dene
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return null;
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async _fetchCompetitorPrices(barcode, platform) {
|
|
280
|
+
// Not: Rakip fiyat çekme genelde web scraping gerektirir
|
|
281
|
+
// Bu versiyonda placeholder - ileride Brave Search veya özel scraper eklenebilir
|
|
282
|
+
return [];
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
async _applyPrice(product, newPrice, platform) {
|
|
286
|
+
const api = platformHub.resolve(platform);
|
|
287
|
+
if (!api?.updatePrice) return false;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const result = await api.updatePrice(product.barcode, newPrice);
|
|
291
|
+
return result?.success || false;
|
|
292
|
+
} catch (e) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
_generateRecommendation(competitors, product) {
|
|
298
|
+
if (!product) return 'Ürün bulunamadı.';
|
|
299
|
+
|
|
300
|
+
const activeCompetitors = competitors.filter(c => c.stock > 0);
|
|
301
|
+
if (activeCompetitors.length === 0) {
|
|
302
|
+
return competitors.length > 0
|
|
303
|
+
? '🔥 Tüm rakipler stoksuz! Fiyatı yükseltebilirsiniz.'
|
|
304
|
+
: 'ℹ️ Rakip verisi bulunamadı.';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const lowestPrice = Math.min(...activeCompetitors.map(c => c.price));
|
|
308
|
+
if (product.price < lowestPrice) {
|
|
309
|
+
return `📈 Fiyatınız en düşük (${product.price} ₺). Artırabilirsiniz.`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return `📊 Rakip fiyat aralığı: ${lowestPrice} - ${Math.max(...activeCompetitors.map(c => c.price))} ₺`;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧠 Sentiment AI Tool
|
|
3
|
+
* Müşteri yorumlarını analiz et ve aksiyon öner
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const sentimentTool = {
|
|
7
|
+
name: 'sentiment',
|
|
8
|
+
|
|
9
|
+
async execute(params, context) {
|
|
10
|
+
const { api, memory, license } = context;
|
|
11
|
+
const { productId, platform = 'all', period = '30d' } = params;
|
|
12
|
+
|
|
13
|
+
if (!license.hasFeature('sentiment')) {
|
|
14
|
+
return { success: false, error: 'Sentiment AI için lisans gerekli.' };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Yorumları topla
|
|
18
|
+
const reviews = await this._fetchReviews(productId, platform, period, api);
|
|
19
|
+
|
|
20
|
+
if (reviews.length === 0) {
|
|
21
|
+
return { success: true, message: 'Analiz edilecek yorum bulunamadı.' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// AI ile analiz
|
|
25
|
+
const analysis = await this._analyzeReviews(reviews, api);
|
|
26
|
+
|
|
27
|
+
// Hafızaya kaydet
|
|
28
|
+
await memory.remember('insight', {
|
|
29
|
+
type: 'sentiment_analysis',
|
|
30
|
+
productId,
|
|
31
|
+
platform,
|
|
32
|
+
analysis
|
|
33
|
+
}, { productId, platform });
|
|
34
|
+
|
|
35
|
+
// Ürün bağlamını güncelle
|
|
36
|
+
await memory.updateProductContext(productId, {
|
|
37
|
+
customerSentiment: {
|
|
38
|
+
positive: analysis.positiveRatio,
|
|
39
|
+
negative: analysis.negativeRatio,
|
|
40
|
+
neutral: analysis.neutralRatio,
|
|
41
|
+
topComplaints: analysis.topComplaints,
|
|
42
|
+
lastAnalyzed: new Date()
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
summary: {
|
|
49
|
+
totalReviews: reviews.length,
|
|
50
|
+
period,
|
|
51
|
+
platform
|
|
52
|
+
},
|
|
53
|
+
sentiment: {
|
|
54
|
+
positive: `${Math.round(analysis.positiveRatio * 100)}%`,
|
|
55
|
+
negative: `${Math.round(analysis.negativeRatio * 100)}%`,
|
|
56
|
+
neutral: `${Math.round(analysis.neutralRatio * 100)}%`,
|
|
57
|
+
averageRating: analysis.averageRating
|
|
58
|
+
},
|
|
59
|
+
insights: {
|
|
60
|
+
topComplaints: analysis.topComplaints,
|
|
61
|
+
topPraises: analysis.topPraises,
|
|
62
|
+
keywords: analysis.keywords
|
|
63
|
+
},
|
|
64
|
+
recommendations: analysis.recommendations,
|
|
65
|
+
suggestedResponses: analysis.suggestedResponses
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async _fetchReviews(productId, platform, period, api) {
|
|
70
|
+
// TODO: Platform API'lerinden yorum çekme
|
|
71
|
+
// Mock data
|
|
72
|
+
return [
|
|
73
|
+
{ rating: 5, text: 'Harika ürün, çok memnunum!', date: new Date() },
|
|
74
|
+
{ rating: 4, text: 'Güzel ama kargo biraz geç geldi.', date: new Date() },
|
|
75
|
+
{ rating: 2, text: 'Kumaş kalitesi beklenenden düşük.', date: new Date() },
|
|
76
|
+
{ rating: 1, text: 'Beden tablosu yanlış, iade ettim.', date: new Date() },
|
|
77
|
+
{ rating: 5, text: 'Fiyat performans oranı çok iyi.', date: new Date() }
|
|
78
|
+
];
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async _analyzeReviews(reviews, api) {
|
|
82
|
+
const positive = reviews.filter(r => r.rating >= 4).length;
|
|
83
|
+
const negative = reviews.filter(r => r.rating <= 2).length;
|
|
84
|
+
const neutral = reviews.filter(r => r.rating === 3).length;
|
|
85
|
+
const total = reviews.length;
|
|
86
|
+
const avgRating = reviews.reduce((sum, r) => sum + r.rating, 0) / total;
|
|
87
|
+
|
|
88
|
+
// TODO: Gerçek NLP analizi için AI kullan
|
|
89
|
+
return {
|
|
90
|
+
positiveRatio: positive / total,
|
|
91
|
+
negativeRatio: negative / total,
|
|
92
|
+
neutralRatio: neutral / total,
|
|
93
|
+
averageRating: avgRating.toFixed(1),
|
|
94
|
+
topComplaints: [
|
|
95
|
+
{ issue: 'Kumaş kalitesi', count: 3, severity: 'high' },
|
|
96
|
+
{ issue: 'Beden tablosu', count: 2, severity: 'medium' },
|
|
97
|
+
{ issue: 'Kargo gecikmesi', count: 1, severity: 'low' }
|
|
98
|
+
],
|
|
99
|
+
topPraises: [
|
|
100
|
+
{ aspect: 'Fiyat performans', count: 5 },
|
|
101
|
+
{ aspect: 'Tasarım', count: 3 }
|
|
102
|
+
],
|
|
103
|
+
keywords: ['kalite', 'fiyat', 'kargo', 'beden', 'güzel'],
|
|
104
|
+
recommendations: [
|
|
105
|
+
'⚠️ Kumaş kalitesi şikayeti yüksek. Tedarikçi ile görüşün.',
|
|
106
|
+
'📏 Beden tablosunu güncelleyin veya detaylı ölçüler ekleyin.',
|
|
107
|
+
'📦 Kargo süresini ürün açıklamasında belirtin.'
|
|
108
|
+
],
|
|
109
|
+
suggestedResponses: {
|
|
110
|
+
negative: 'Yaşadığınız sorun için özür dileriz. Sizinle iletişime geçip sorunu çözmek istiyoruz. Lütfen sipariş numaranızla bize ulaşın.',
|
|
111
|
+
positive: 'Güzel yorumunuz için teşekkür ederiz! 🙏 Sizi mutlu etmek en büyük motivasyonumuz.'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 👁️ Vision AI Tool
|
|
3
|
+
* Fotoğraftan ürün bilgisi çıkarma ve otomatik listeleme
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
|
|
10
|
+
export const visionTool = {
|
|
11
|
+
name: 'vision',
|
|
12
|
+
|
|
13
|
+
async execute(params, context) {
|
|
14
|
+
const { api, memory, license } = context;
|
|
15
|
+
const { imageUrl, targetPlatforms = ['trendyol'], autoPublish = false } = params;
|
|
16
|
+
|
|
17
|
+
// Lisans kontrolü
|
|
18
|
+
if (!license.hasFeature('vision')) {
|
|
19
|
+
return { success: false, error: 'Vision AI için lisans gerekli.' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// AI Config kontrolü
|
|
23
|
+
const aiConfig = api.config.get('models.openai') || api.config.get('models.anthropic');
|
|
24
|
+
if (!aiConfig?.apiKey) {
|
|
25
|
+
return { success: false, error: 'AI API anahtarı yapılandırılmamış.' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Görsel analizi
|
|
30
|
+
const analysis = await this._analyzeImage(imageUrl, aiConfig);
|
|
31
|
+
|
|
32
|
+
// Kategori eşleştirme (her platform için)
|
|
33
|
+
const categoryMappings = await this._mapCategories(analysis, targetPlatforms);
|
|
34
|
+
|
|
35
|
+
// SEO optimizasyonu
|
|
36
|
+
const seoContent = this._generateSeoContent(analysis);
|
|
37
|
+
|
|
38
|
+
// Hafızaya kaydet
|
|
39
|
+
await memory.remember('product', {
|
|
40
|
+
type: 'vision_analysis',
|
|
41
|
+
analysis,
|
|
42
|
+
seoContent,
|
|
43
|
+
categoryMappings
|
|
44
|
+
}, {
|
|
45
|
+
imageUrl,
|
|
46
|
+
platforms: targetPlatforms
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const result = {
|
|
50
|
+
success: true,
|
|
51
|
+
analysis: {
|
|
52
|
+
detected: analysis.detected,
|
|
53
|
+
confidence: analysis.confidence,
|
|
54
|
+
attributes: analysis.attributes
|
|
55
|
+
},
|
|
56
|
+
listing: {
|
|
57
|
+
title: seoContent.title,
|
|
58
|
+
description: seoContent.description,
|
|
59
|
+
keywords: seoContent.keywords,
|
|
60
|
+
suggestedPrice: analysis.suggestedPrice
|
|
61
|
+
},
|
|
62
|
+
categories: categoryMappings,
|
|
63
|
+
published: []
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Otomatik yayınla
|
|
67
|
+
if (autoPublish) {
|
|
68
|
+
for (const platform of targetPlatforms) {
|
|
69
|
+
try {
|
|
70
|
+
const publishResult = await this._publishToPlatform(platform, {
|
|
71
|
+
...result.listing,
|
|
72
|
+
category: categoryMappings[platform],
|
|
73
|
+
images: [imageUrl]
|
|
74
|
+
}, api);
|
|
75
|
+
|
|
76
|
+
result.published.push({
|
|
77
|
+
platform,
|
|
78
|
+
success: publishResult.success,
|
|
79
|
+
productId: publishResult.productId
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
result.published.push({
|
|
83
|
+
platform,
|
|
84
|
+
success: false,
|
|
85
|
+
error: err.message
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
|
|
93
|
+
} catch (error) {
|
|
94
|
+
api.logger.error('Vision AI hatası:', error);
|
|
95
|
+
return { success: false, error: error.message };
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async _analyzeImage(imageUrl, aiConfig) {
|
|
100
|
+
// Görsel base64'e çevir (eğer URL ise)
|
|
101
|
+
let imageData = imageUrl;
|
|
102
|
+
|
|
103
|
+
if (imageUrl.startsWith('http')) {
|
|
104
|
+
const response = await axios.get(imageUrl, { responseType: 'arraybuffer' });
|
|
105
|
+
imageData = `data:image/jpeg;base64,${Buffer.from(response.data).toString('base64')}`;
|
|
106
|
+
} else if (fs.existsSync(imageUrl)) {
|
|
107
|
+
const buffer = fs.readFileSync(imageUrl);
|
|
108
|
+
imageData = `data:image/jpeg;base64,${buffer.toString('base64')}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// OpenAI Vision API
|
|
112
|
+
const response = await axios.post(`${aiConfig.baseUrl || 'https://api.openai.com/v1'}/chat/completions`, {
|
|
113
|
+
model: 'gpt-4o',
|
|
114
|
+
messages: [
|
|
115
|
+
{
|
|
116
|
+
role: 'user',
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: `Bu ürün fotoğrafını analiz et ve şu bilgileri JSON formatında ver:
|
|
121
|
+
{
|
|
122
|
+
"detected": "Ürün tipi (örn: Kadın Tişört)",
|
|
123
|
+
"confidence": 0.95,
|
|
124
|
+
"attributes": {
|
|
125
|
+
"color": "Renk",
|
|
126
|
+
"material": "Malzeme",
|
|
127
|
+
"style": "Stil",
|
|
128
|
+
"size_type": "Beden tipi (standart/plus size vb)",
|
|
129
|
+
"pattern": "Desen",
|
|
130
|
+
"brand_indicators": "Marka işaretleri varsa"
|
|
131
|
+
},
|
|
132
|
+
"suggestedPrice": {
|
|
133
|
+
"min": 100,
|
|
134
|
+
"max": 200,
|
|
135
|
+
"optimal": 149
|
|
136
|
+
},
|
|
137
|
+
"seo_keywords": ["anahtar kelime 1", "anahtar kelime 2"],
|
|
138
|
+
"target_audience": "Hedef kitle"
|
|
139
|
+
}`
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'image_url',
|
|
143
|
+
image_url: { url: imageData }
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
max_tokens: 1000
|
|
149
|
+
}, {
|
|
150
|
+
headers: {
|
|
151
|
+
'Authorization': `Bearer ${aiConfig.apiKey}`,
|
|
152
|
+
'Content-Type': 'application/json'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const content = response.data.choices[0].message.content;
|
|
157
|
+
// JSON bloğunu çıkar
|
|
158
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
159
|
+
if (jsonMatch) {
|
|
160
|
+
return JSON.parse(jsonMatch[0]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
throw new Error('AI yanıtı parse edilemedi');
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async _mapCategories(analysis, platforms) {
|
|
167
|
+
const mappings = {};
|
|
168
|
+
|
|
169
|
+
// Platform-spesifik kategori eşleştirme
|
|
170
|
+
// Gerçek implementasyonda platform kategori API'leri kullanılır
|
|
171
|
+
const categoryMap = {
|
|
172
|
+
trendyol: {
|
|
173
|
+
'Kadın Tişört': 'Kadın > Giyim > Tişört > V Yaka',
|
|
174
|
+
'Erkek Gömlek': 'Erkek > Giyim > Gömlek > Uzun Kollu',
|
|
175
|
+
'Telefon Kılıfı': 'Elektronik > Telefon Aksesuarları > Kılıflar'
|
|
176
|
+
},
|
|
177
|
+
hepsiburada: {
|
|
178
|
+
'Kadın Tişört': 'Moda > Kadın Giyim > Üst Giyim > Tişört',
|
|
179
|
+
'Erkek Gömlek': 'Moda > Erkek Giyim > Gömlek',
|
|
180
|
+
'Telefon Kılıfı': 'Telefon & Aksesuar > Telefon Kılıfları'
|
|
181
|
+
},
|
|
182
|
+
amazon_de: {
|
|
183
|
+
'Kadın Tişört': 'Bekleidung > Damen > Oberteile > T-Shirts',
|
|
184
|
+
'Erkek Gömlek': 'Bekleidung > Herren > Hemden',
|
|
185
|
+
'Telefon Kılıfı': 'Elektronik > Handys > Hüllen'
|
|
186
|
+
},
|
|
187
|
+
n11: {
|
|
188
|
+
'Kadın Tişört': 'Giyim & Aksesuar > Kadın Giyim > Tişört',
|
|
189
|
+
'Erkek Gömlek': 'Giyim & Aksesuar > Erkek Giyim > Gömlek',
|
|
190
|
+
'Telefon Kılıfı': 'Elektronik > Telefon Aksesuarları > Kılıf'
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
for (const platform of platforms) {
|
|
195
|
+
const platformMap = categoryMap[platform] || {};
|
|
196
|
+
mappings[platform] = platformMap[analysis.detected] || 'Genel > Diğer';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return mappings;
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
_generateSeoContent(analysis) {
|
|
203
|
+
const { detected, attributes, seo_keywords = [] } = analysis;
|
|
204
|
+
|
|
205
|
+
// SEO uyumlu başlık oluştur
|
|
206
|
+
const titleParts = [];
|
|
207
|
+
if (attributes.material) titleParts.push(attributes.material);
|
|
208
|
+
if (attributes.color) titleParts.push(attributes.color);
|
|
209
|
+
if (detected) titleParts.push(detected);
|
|
210
|
+
if (attributes.style) titleParts.push(attributes.style);
|
|
211
|
+
|
|
212
|
+
const title = titleParts.join(' ').slice(0, 100);
|
|
213
|
+
|
|
214
|
+
// Açıklama oluştur
|
|
215
|
+
const description = `
|
|
216
|
+
${title}
|
|
217
|
+
|
|
218
|
+
✨ Ürün Özellikleri:
|
|
219
|
+
${attributes.material ? `• Malzeme: ${attributes.material}` : ''}
|
|
220
|
+
${attributes.color ? `• Renk: ${attributes.color}` : ''}
|
|
221
|
+
${attributes.style ? `• Stil: ${attributes.style}` : ''}
|
|
222
|
+
${attributes.pattern ? `• Desen: ${attributes.pattern}` : ''}
|
|
223
|
+
|
|
224
|
+
🛒 Neden Bu Ürün?
|
|
225
|
+
• Yüksek kaliteli malzeme
|
|
226
|
+
• Şık ve modern tasarım
|
|
227
|
+
• Rahat kullanım
|
|
228
|
+
• Hızlı kargo
|
|
229
|
+
|
|
230
|
+
📦 Kargo Bilgisi:
|
|
231
|
+
Siparişiniz aynı gün kargoya verilir.
|
|
232
|
+
|
|
233
|
+
⭐ Müşteri Memnuniyeti:
|
|
234
|
+
Tüm ürünlerimiz kalite kontrol sürecinden geçmektedir.
|
|
235
|
+
|
|
236
|
+
#${seo_keywords.join(' #')}
|
|
237
|
+
`.trim();
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
title,
|
|
241
|
+
description,
|
|
242
|
+
keywords: seo_keywords,
|
|
243
|
+
shortDescription: titleParts.join(' ')
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
async _publishToPlatform(platform, listingData, api) {
|
|
248
|
+
// TODO: Platform API'lerine ürün yayınlama
|
|
249
|
+
api.logger.info(`📤 ${platform}'a yayınlanıyor: ${listingData.title}`);
|
|
250
|
+
|
|
251
|
+
// Mock response
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
productId: `${platform}_${Date.now()}`
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
};
|