vantuz 3.2.7 → 3.3.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/core/ai-provider.js +95 -141
- package/core/database.js +70 -60
- package/core/eia-brain.js +102 -0
- package/core/eia-monitor.js +220 -0
- package/core/engine.js +99 -39
- package/core/gateway.js +5 -21
- package/core/migrations/001-initial-schema.js +28 -0
- package/core/scheduler.js +80 -0
- package/core/scrapers/Scraper.js +61 -0
- package/core/scrapers/TrendyolScraper.js +76 -0
- package/core/vector-db.js +97 -0
- package/onboard.js +27 -1
- package/package.json +7 -5
- package/plugins/vantuz/platforms/index.js +13 -9
- package/server/app.js +2 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// core/scrapers/TrendyolScraper.js
|
|
2
|
+
import { Scraper } from './Scraper.js';
|
|
3
|
+
|
|
4
|
+
export class TrendyolScraper extends Scraper {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
this.baseUrl = 'https://www.trendyol.com';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async getProductPrice(productUrl) {
|
|
11
|
+
await this.init();
|
|
12
|
+
await this.goTo(productUrl);
|
|
13
|
+
|
|
14
|
+
// Example: Extract price. This selector might need adjustment based on actual Trendyol HTML.
|
|
15
|
+
const priceSelector = '.pr-new-price'; // Or similar selector for price
|
|
16
|
+
const priceText = await this.extractText(priceSelector);
|
|
17
|
+
|
|
18
|
+
await this.close();
|
|
19
|
+
return priceText ? parseFloat(priceText.replace(/[^\d,]/g, '').replace(',', '.')) : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async searchProducts(query) {
|
|
23
|
+
await this.init();
|
|
24
|
+
await this.goTo(`${this.baseUrl}/sr?q=${encodeURIComponent(query)}`);
|
|
25
|
+
|
|
26
|
+
// Example: Extract product titles and URLs from search results
|
|
27
|
+
const productTitles = await this.extractAllText('.p-card-wrppr .prdct-desc-v2 a span');
|
|
28
|
+
const productLinks = await this.page.locator('.p-card-wrppr .prdct-desc-v2 a').evaluateAll((links) => links.map(link => link.href));
|
|
29
|
+
|
|
30
|
+
const products = productTitles.map((title, index) => ({
|
|
31
|
+
title,
|
|
32
|
+
url: productLinks[index]
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
await this.close();
|
|
36
|
+
return products;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async getPriceChanges(productUrl) {
|
|
40
|
+
await this.init();
|
|
41
|
+
await this.goTo(productUrl);
|
|
42
|
+
// Placeholder for logic to detect price changes over time
|
|
43
|
+
// This would involve comparing current price with a stored baseline
|
|
44
|
+
const currentPrice = await this.getProductPrice(productUrl); // Re-use existing method
|
|
45
|
+
await this.close();
|
|
46
|
+
return { productUrl, currentPrice, status: 'Price change detection not fully implemented yet' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getBuyboxStatus(productUrl) {
|
|
50
|
+
await this.init();
|
|
51
|
+
await this.goTo(productUrl);
|
|
52
|
+
// Placeholder for logic to determine buybox status (won/lost)
|
|
53
|
+
// This might involve checking specific elements or seller names on the product page
|
|
54
|
+
await this.close();
|
|
55
|
+
return { productUrl, status: 'Buybox status detection not fully implemented yet', won: false };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getStockMovements(productUrl) {
|
|
59
|
+
await this.init();
|
|
60
|
+
await this.goTo(productUrl);
|
|
61
|
+
// Placeholder for logic to track stock changes
|
|
62
|
+
// This might involve monitoring "out of stock" indicators or specific stock counts if available
|
|
63
|
+
await this.close();
|
|
64
|
+
return { productUrl, status: 'Stock movement detection not fully implemented yet', inStock: true };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async getProductReviewsAndRatings(productUrl) {
|
|
68
|
+
await this.init();
|
|
69
|
+
await this.goTo(productUrl);
|
|
70
|
+
// Placeholder for logic to scrape reviews and ratings
|
|
71
|
+
// This often involves navigating to review sections or pagination
|
|
72
|
+
const reviews = ['Placeholder review 1', 'Placeholder review 2'];
|
|
73
|
+
const rating = 4.5; // Placeholder rating
|
|
74
|
+
await this.close();
|
|
75
|
+
return { productUrl, reviews, rating, status: 'Review and rating scraping not fully implemented yet' };
|
|
76
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// core/vector-db.js
|
|
2
|
+
import { log } from './ai-provider.js';
|
|
3
|
+
|
|
4
|
+
class VectorDB {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.collections = new Map(); // collectionName -> { vectors: [], metadatas: [] }
|
|
7
|
+
log('INFO', 'In-memory VectorDB initialized');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Kosinüs benzerliğini hesaplar.
|
|
12
|
+
* @param {number[]} vec1
|
|
13
|
+
* @param {number[]} vec2
|
|
14
|
+
* @returns {number} Kosinüs benzerliği.
|
|
15
|
+
*/
|
|
16
|
+
_cosineSimilarity(vec1, vec2) {
|
|
17
|
+
if (vec1.length !== vec2.length) {
|
|
18
|
+
throw new Error('Vectors must be of the same dimension');
|
|
19
|
+
}
|
|
20
|
+
let dotProduct = 0;
|
|
21
|
+
let magnitude1 = 0;
|
|
22
|
+
let magnitude2 = 0;
|
|
23
|
+
for (let i = 0; i < vec1.length; i++) {
|
|
24
|
+
dotProduct += vec1[i] * vec2[i];
|
|
25
|
+
magnitude1 += vec1[i] * vec1[i];
|
|
26
|
+
magnitude2 += vec2[i] * vec2[i];
|
|
27
|
+
}
|
|
28
|
+
magnitude1 = Math.sqrt(magnitude1);
|
|
29
|
+
magnitude2 = Math.sqrt(magnitude2);
|
|
30
|
+
|
|
31
|
+
if (magnitude1 === 0 || magnitude2 === 0) {
|
|
32
|
+
return 0; // Avoid division by zero
|
|
33
|
+
}
|
|
34
|
+
return dotProduct / (magnitude1 * magnitude2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Vektör koleksiyonu oluşturur veya alır.
|
|
39
|
+
* @param {string} collectionName
|
|
40
|
+
* @returns {{vectors: Array<number[]>, metadatas: Array<Object>}}
|
|
41
|
+
*/
|
|
42
|
+
getCollection(collectionName) {
|
|
43
|
+
if (!this.collections.has(collectionName)) {
|
|
44
|
+
this.collections.set(collectionName, { vectors: [], metadatas: [] });
|
|
45
|
+
log('INFO', `Vector collection "${collectionName}" created.`);
|
|
46
|
+
}
|
|
47
|
+
return this.collections.get(collectionName);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Koleksiyona vektör ve metadata ekler.
|
|
52
|
+
* @param {string} collectionName
|
|
53
|
+
* @param {number[]} vector
|
|
54
|
+
* @param {Object} metadata
|
|
55
|
+
*/
|
|
56
|
+
add(collectionName, vector, metadata) {
|
|
57
|
+
const collection = this.getCollection(collectionName);
|
|
58
|
+
collection.vectors.push(vector);
|
|
59
|
+
collection.metadatas.push(metadata);
|
|
60
|
+
log('INFO', `Vector added to collection "${collectionName}"`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Bir sorgu vektörüne en benzer vektörleri bulur.
|
|
65
|
+
* @param {string} collectionName
|
|
66
|
+
* @param {number[]} queryVector
|
|
67
|
+
* @param {number} topK En çok kaç sonuç döndürülecek.
|
|
68
|
+
* @returns {Array<{metadata: Object, similarity: number}>} Sıralanmış sonuçlar.
|
|
69
|
+
*/
|
|
70
|
+
search(collectionName, queryVector, topK = 5) {
|
|
71
|
+
const collection = this.collections.get(collectionName);
|
|
72
|
+
if (!collection || collection.vectors.length === 0) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const results = collection.vectors.map((vector, index) => {
|
|
77
|
+
const similarity = this._cosineSimilarity(queryVector, vector);
|
|
78
|
+
return {
|
|
79
|
+
metadata: collection.metadatas[index],
|
|
80
|
+
similarity: similarity
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
results.sort((a, b) => b.similarity - a.similarity); // Yüksek benzerlik önde
|
|
85
|
+
log('INFO', `Search performed on collection "${collectionName}", found ${results.length} results.`);
|
|
86
|
+
return results.slice(0, topK);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let vectorDbInstance = null;
|
|
91
|
+
|
|
92
|
+
export function getVectorDB() {
|
|
93
|
+
if (!vectorDbInstance) {
|
|
94
|
+
vectorDbInstance = new VectorDB();
|
|
95
|
+
}
|
|
96
|
+
return vectorDbInstance;
|
|
97
|
+
}
|
package/onboard.js
CHANGED
|
@@ -50,7 +50,32 @@ class OnboardingWizard {
|
|
|
50
50
|
constructor() {
|
|
51
51
|
this.envVars = {};
|
|
52
52
|
this.step = 0;
|
|
53
|
-
this.totalSteps =
|
|
53
|
+
this.totalSteps = 6; // Updated total steps
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async step_EIAConfig() {
|
|
57
|
+
this.step = 5; // Adjust step number
|
|
58
|
+
this.printHeader('E-TICARET YÖNETİM AJANSI (EIA) YAPILANDIRMASI');
|
|
59
|
+
|
|
60
|
+
console.log('EIA\'nın operasyonlarını optimize etmek için bazı bilgiler sağlayın.\n');
|
|
61
|
+
|
|
62
|
+
const competitorUrls = await this.prompt('Rakip Ürün URL\'leri (virgülle ayırarak): ');
|
|
63
|
+
if (competitorUrls) {
|
|
64
|
+
this.envVars.EIA_COMPETITOR_URLS = competitorUrls.trim();
|
|
65
|
+
console.log(c('green', '[OK] Rakip URL\'leri kaydedildi.\n'));
|
|
66
|
+
} else {
|
|
67
|
+
console.log(c('yellow', '[BİLGİ] Rakip URL\'leri girilmedi.\n'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const profitMargin = await this.prompt('Hedef Kar Marjı (%) [15]: ');
|
|
71
|
+
if (profitMargin && !isNaN(parseFloat(profitMargin))) {
|
|
72
|
+
this.envVars.EIA_TARGET_PROFIT_MARGIN = parseFloat(profitMargin);
|
|
73
|
+
console.log(c('green', '[OK] Hedef Kar Marjı kaydedildi.\n'));
|
|
74
|
+
} else {
|
|
75
|
+
this.envVars.EIA_TARGET_PROFIT_MARGIN = 15; // Default if invalid
|
|
76
|
+
console.log(c('yellow', '[BİLGİ] Geçersiz kar marjı, varsayılan %15 kullanılıyor.\n'));
|
|
77
|
+
}
|
|
78
|
+
await sleep(1000);
|
|
54
79
|
}
|
|
55
80
|
|
|
56
81
|
clear() { console.clear(); }
|
|
@@ -70,6 +95,7 @@ class OnboardingWizard {
|
|
|
70
95
|
await this.step2_Platforms();
|
|
71
96
|
await this.step3_Channels();
|
|
72
97
|
await this.step4_Gateway();
|
|
98
|
+
await this.step_EIAConfig(); // New step
|
|
73
99
|
await this.step5_Save();
|
|
74
100
|
await this.showSuccess();
|
|
75
101
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vantuz",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Yapay Zeka Destekli E-Ticaret Yönetim Platformu - 7 Pazaryeri + WhatsApp/Telegram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "cli.js",
|
|
@@ -55,16 +55,18 @@
|
|
|
55
55
|
"url": "https://github.com/nuricanavsar/vantuz/issues"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"axios": "^1.
|
|
59
|
-
"body-parser": "^2.2.2",
|
|
58
|
+
"axios": "^1.13.5",
|
|
60
59
|
"cors": "^2.8.6",
|
|
60
|
+
"cron": "^4.4.0",
|
|
61
61
|
"dotenv": "^16.0.0",
|
|
62
62
|
"express": "^5.2.1",
|
|
63
63
|
"openclaw": "^2026.2.9",
|
|
64
|
+
"sqlite3": "^5.1.7",
|
|
64
65
|
"xml2js": "^0.6.2"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
|
-
"eslint": "^8.0.0"
|
|
68
|
+
"eslint": "^8.0.0",
|
|
69
|
+
"playwright": "^1.58.2"
|
|
68
70
|
},
|
|
69
71
|
"os": [
|
|
70
72
|
"darwin",
|
|
@@ -86,4 +88,4 @@
|
|
|
86
88
|
"README.md",
|
|
87
89
|
"DOCS_TR.md"
|
|
88
90
|
]
|
|
89
|
-
}
|
|
91
|
+
}
|
|
@@ -173,20 +173,24 @@ export const platformHub = {
|
|
|
173
173
|
const connected = this.getConnected();
|
|
174
174
|
const allOrders = [];
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
const orderPromises = connected.map(async (platform) => {
|
|
177
177
|
const api = platforms[platform];
|
|
178
178
|
const result = await api.getOrders(params);
|
|
179
179
|
if (result.success) {
|
|
180
180
|
const orders = result.data.content || result.data.orders || result.data || [];
|
|
181
|
-
orders.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
});
|
|
187
|
-
});
|
|
181
|
+
return orders.map(order => ({
|
|
182
|
+
...order,
|
|
183
|
+
_platform: platform,
|
|
184
|
+
_icon: this.getIcon(platform)
|
|
185
|
+
}));
|
|
188
186
|
}
|
|
189
|
-
|
|
187
|
+
return [];
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const results = await Promise.all(orderPromises);
|
|
191
|
+
results.forEach(platformOrders => {
|
|
192
|
+
allOrders.push(...platformOrders);
|
|
193
|
+
});
|
|
190
194
|
|
|
191
195
|
// Tarihe göre sırala
|
|
192
196
|
allOrders.sort((a, b) => new Date(b.createdDate || b.orderDate) - new Date(a.createdDate || a.orderDate));
|
package/server/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import cors from 'cors';
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { getEngine } from '../core/engine.js';
|
|
@@ -16,7 +16,7 @@ const PORT = process.env.PORT || 3001;
|
|
|
16
16
|
|
|
17
17
|
// Middleware
|
|
18
18
|
app.use(cors());
|
|
19
|
-
app.use(
|
|
19
|
+
app.use(express.json());
|
|
20
20
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
21
21
|
|
|
22
22
|
// Engine Singleton
|