vantuz 3.3.6 → 3.3.7
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/README.md +62 -26
- package/config.js +24 -0
- package/core/agent-loop.js +190 -0
- package/core/ai-provider.js +41 -7
- package/core/automation.js +43 -24
- package/core/dashboard.js +230 -0
- package/core/engine.js +31 -4
- package/core/learning.js +214 -0
- package/core/marketplace-adapter.js +168 -0
- package/core/memory.js +190 -0
- package/core/queue.js +120 -0
- package/core/scheduler.js +111 -31
- package/core/self-healer.js +314 -0
- package/core/unified-product.js +214 -0
- package/core/vector-db.js +83 -17
- package/core/vision-service.js +113 -0
- package/package.json +1 -1
- package/server/app.js +142 -29
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// core/vision-service.js
|
|
2
|
+
// Decoupled Vision AI Service
|
|
3
|
+
// Extracted from plugins/vantuz/tools/vision.js for API/Gateway access.
|
|
4
|
+
// Can be called from CLI, API endpoint, or Warehouse Node.
|
|
5
|
+
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import { log } from './ai-provider.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Analyze a product image and return structured data.
|
|
12
|
+
* Accepts: file path, URL, or raw Base64 string.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} imageInput - File path, URL, or base64 data URI.
|
|
15
|
+
* @param {object} aiConfig - { apiKey, baseUrl?, model? }
|
|
16
|
+
* @returns {object} { detected, confidence, attributes, suggestedPrice, seo_keywords }
|
|
17
|
+
*/
|
|
18
|
+
export async function analyzeProductImage(imageInput, aiConfig) {
|
|
19
|
+
if (!aiConfig?.apiKey) {
|
|
20
|
+
throw new Error('AI API anahtarı gerekli (aiConfig.apiKey)');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Normalize image to base64 data URI
|
|
24
|
+
let imageData = imageInput;
|
|
25
|
+
|
|
26
|
+
if (imageInput.startsWith('http://') || imageInput.startsWith('https://')) {
|
|
27
|
+
log('INFO', 'Vision: Downloading image from URL...');
|
|
28
|
+
const response = await axios.get(imageInput, { responseType: 'arraybuffer', timeout: 15000 });
|
|
29
|
+
imageData = `data:image/jpeg;base64,${Buffer.from(response.data).toString('base64')}`;
|
|
30
|
+
} else if (imageInput.startsWith('data:')) {
|
|
31
|
+
// Already a data URI
|
|
32
|
+
imageData = imageInput;
|
|
33
|
+
} else if (fs.existsSync(imageInput)) {
|
|
34
|
+
log('INFO', 'Vision: Reading local file...');
|
|
35
|
+
const buffer = fs.readFileSync(imageInput);
|
|
36
|
+
imageData = `data:image/jpeg;base64,${buffer.toString('base64')}`;
|
|
37
|
+
} else {
|
|
38
|
+
// Assume raw base64 string
|
|
39
|
+
imageData = `data:image/jpeg;base64,${imageInput}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const model = aiConfig.model || 'gpt-4o';
|
|
43
|
+
const baseUrl = aiConfig.baseUrl || 'https://api.openai.com/v1';
|
|
44
|
+
|
|
45
|
+
log('INFO', `Vision: Analyzing with ${model}...`);
|
|
46
|
+
|
|
47
|
+
const response = await axios.post(`${baseUrl}/chat/completions`, {
|
|
48
|
+
model,
|
|
49
|
+
messages: [
|
|
50
|
+
{
|
|
51
|
+
role: 'user',
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: 'text',
|
|
55
|
+
text: `Bu ürün fotoğrafını analiz et. JSON formatında yanıt ver:
|
|
56
|
+
{
|
|
57
|
+
"detected": "Ürün tipi",
|
|
58
|
+
"confidence": 0.95,
|
|
59
|
+
"attributes": {
|
|
60
|
+
"color": "Renk",
|
|
61
|
+
"material": "Malzeme",
|
|
62
|
+
"style": "Stil",
|
|
63
|
+
"pattern": "Desen",
|
|
64
|
+
"condition": "Durum (yeni/kullanılmış/hasarlı)"
|
|
65
|
+
},
|
|
66
|
+
"suggestedPrice": { "min": 100, "max": 200, "optimal": 149 },
|
|
67
|
+
"seo_keywords": ["anahtar1", "anahtar2"],
|
|
68
|
+
"defects": "Görünür hasar/kusur varsa açıkla, yoksa null"
|
|
69
|
+
}`
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'image_url',
|
|
73
|
+
image_url: { url: imageData }
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
max_tokens: 1000
|
|
79
|
+
}, {
|
|
80
|
+
headers: {
|
|
81
|
+
'Authorization': `Bearer ${aiConfig.apiKey}`,
|
|
82
|
+
'Content-Type': 'application/json'
|
|
83
|
+
},
|
|
84
|
+
timeout: 30000
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const content = response.data.choices[0].message.content;
|
|
88
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
89
|
+
if (jsonMatch) {
|
|
90
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
91
|
+
log('INFO', 'Vision: Analysis complete', { detected: result.detected });
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error('AI Vision yanıtı parse edilemedi');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Quick damage check for returns.
|
|
100
|
+
* @param {string} imageInput - Image path/URL/base64.
|
|
101
|
+
* @param {object} aiConfig - AI config.
|
|
102
|
+
* @returns {object} { isDamaged, severity, description }
|
|
103
|
+
*/
|
|
104
|
+
export async function checkReturnDamage(imageInput, aiConfig) {
|
|
105
|
+
const analysis = await analyzeProductImage(imageInput, aiConfig);
|
|
106
|
+
return {
|
|
107
|
+
isDamaged: !!analysis.defects,
|
|
108
|
+
severity: analysis.defects ? 'check_required' : 'ok',
|
|
109
|
+
condition: analysis.attributes?.condition || 'unknown',
|
|
110
|
+
description: analysis.defects || 'Görünür hasar tespit edilmedi.',
|
|
111
|
+
fullAnalysis: analysis
|
|
112
|
+
};
|
|
113
|
+
}
|
package/package.json
CHANGED
package/server/app.js
CHANGED
|
@@ -61,35 +61,35 @@ app.get('/api/orders', async (req, res) => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
// 4. AI Chat
|
|
64
|
-
app.post('/api/chat', async (req, res) => {
|
|
65
|
-
const { message } = req.body;
|
|
66
|
-
if (!message) return res.status(400).json({ error: 'Mesaj gerekli' });
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const instance = await initEngine();
|
|
70
|
-
const response = await instance.chat(message);
|
|
71
|
-
res.json({ response });
|
|
72
|
-
} catch (e) {
|
|
73
|
-
res.status(500).json({ error: e.message });
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// 4b. Inbound message (channels)
|
|
78
|
-
app.post('/api/inbound', async (req, res) => {
|
|
79
|
-
const { message, channel, from } = req.body || {};
|
|
80
|
-
if (!message) return res.status(400).json({ error: 'Mesaj gerekli' });
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const instance = await initEngine();
|
|
84
|
-
const response = await instance.handleMessage(message, {
|
|
85
|
-
channel: channel || 'local',
|
|
86
|
-
from: from || 'unknown'
|
|
87
|
-
});
|
|
88
|
-
res.json({ response });
|
|
89
|
-
} catch (e) {
|
|
90
|
-
res.status(500).json({ error: e.message });
|
|
91
|
-
}
|
|
92
|
-
});
|
|
64
|
+
app.post('/api/chat', async (req, res) => {
|
|
65
|
+
const { message } = req.body;
|
|
66
|
+
if (!message) return res.status(400).json({ error: 'Mesaj gerekli' });
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const instance = await initEngine();
|
|
70
|
+
const response = await instance.chat(message);
|
|
71
|
+
res.json({ response });
|
|
72
|
+
} catch (e) {
|
|
73
|
+
res.status(500).json({ error: e.message });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 4b. Inbound message (channels)
|
|
78
|
+
app.post('/api/inbound', async (req, res) => {
|
|
79
|
+
const { message, channel, from } = req.body || {};
|
|
80
|
+
if (!message) return res.status(400).json({ error: 'Mesaj gerekli' });
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const instance = await initEngine();
|
|
84
|
+
const response = await instance.handleMessage(message, {
|
|
85
|
+
channel: channel || 'local',
|
|
86
|
+
from: from || 'unknown'
|
|
87
|
+
});
|
|
88
|
+
res.json({ response });
|
|
89
|
+
} catch (e) {
|
|
90
|
+
res.status(500).json({ error: e.message });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
93
|
|
|
94
94
|
// 5. Logs
|
|
95
95
|
app.get('/api/logs', async (req, res) => {
|
|
@@ -134,6 +134,119 @@ app.get('/api/doctor', async (req, res) => {
|
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
138
|
+
// 9. WEBHOOKS - Platform Event Receivers
|
|
139
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
140
|
+
|
|
141
|
+
app.post('/api/webhooks/:platform', async (req, res) => {
|
|
142
|
+
const { platform } = req.params;
|
|
143
|
+
const secret = req.headers['x-webhook-secret'] || '';
|
|
144
|
+
const expectedSecret = process.env.WEBHOOK_SECRET || '';
|
|
145
|
+
|
|
146
|
+
if (expectedSecret && secret !== expectedSecret) {
|
|
147
|
+
log('WARN', `Webhook rejected: invalid secret`, { platform });
|
|
148
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const event = req.body;
|
|
152
|
+
log('INFO', `Webhook received from ${platform}`, {
|
|
153
|
+
type: event.type || event.eventType || 'unknown'
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const instance = await initEngine();
|
|
158
|
+
const eventType = (event.type || event.eventType || '').toLowerCase();
|
|
159
|
+
|
|
160
|
+
if (eventType.includes('order') || eventType.includes('siparis')) {
|
|
161
|
+
await instance.chat(`📦 [${platform.toUpperCase()}] Yeni sipariş olayı: ${eventType}`);
|
|
162
|
+
} else if (eventType.includes('stock') || eventType.includes('stok')) {
|
|
163
|
+
await instance.chat(`📊 [${platform.toUpperCase()}] Stok olayı: ${eventType}`);
|
|
164
|
+
} else if (eventType.includes('return') || eventType.includes('iade')) {
|
|
165
|
+
await instance.chat(`🔄 [${platform.toUpperCase()}] İade talebi alındı`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (instance.memory) {
|
|
169
|
+
instance.memory.remember(`${platform}: ${eventType}`, 'webhook');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
res.json({ received: true, platform, eventType });
|
|
173
|
+
} catch (e) {
|
|
174
|
+
res.status(500).json({ error: e.message });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
179
|
+
// 10. VISION API
|
|
180
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
181
|
+
|
|
182
|
+
app.post('/api/vision/analyze', async (req, res) => {
|
|
183
|
+
const { image, checkDamage } = req.body;
|
|
184
|
+
if (!image) return res.status(400).json({ error: 'image parametresi gerekli' });
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const { analyzeProductImage, checkReturnDamage } = await import('../core/vision-service.js');
|
|
188
|
+
const instance = await initEngine();
|
|
189
|
+
const aiConfig = {
|
|
190
|
+
apiKey: instance.env?.OPENAI_API_KEY || instance.env?.GEMINI_API_KEY,
|
|
191
|
+
model: 'gpt-4o'
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const result = checkDamage
|
|
195
|
+
? await checkReturnDamage(image, aiConfig)
|
|
196
|
+
: await analyzeProductImage(image, aiConfig);
|
|
197
|
+
res.json(result);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
res.status(500).json({ error: e.message });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
204
|
+
// 11. QUEUE & MEMORY API
|
|
205
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
206
|
+
|
|
207
|
+
app.get('/api/queue', async (req, res) => {
|
|
208
|
+
const instance = await initEngine();
|
|
209
|
+
res.json(instance.queue ? instance.queue.getStatus() : { error: 'Queue not initialized' });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
app.get('/api/memory', async (req, res) => {
|
|
213
|
+
const instance = await initEngine();
|
|
214
|
+
if (!instance.memory) return res.json({ error: 'Memory not initialized' });
|
|
215
|
+
res.json({
|
|
216
|
+
factsCount: instance.memory.facts.length,
|
|
217
|
+
strategiesCount: instance.memory.strategies.length,
|
|
218
|
+
recentFacts: instance.memory.getRecentFacts(10),
|
|
219
|
+
strategies: instance.memory.getStrategies()
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
app.post('/api/memory/remember', async (req, res) => {
|
|
224
|
+
const { fact, category } = req.body;
|
|
225
|
+
if (!fact) return res.status(400).json({ error: 'fact gerekli' });
|
|
226
|
+
const instance = await initEngine();
|
|
227
|
+
if (!instance.memory) return res.json({ error: 'Memory not initialized' });
|
|
228
|
+
res.json(instance.memory.remember(fact, category || 'general'));
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
232
|
+
// 12. SYSTEM HEALTH DASHBOARD
|
|
233
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
234
|
+
|
|
235
|
+
app.get('/api/dashboard', async (req, res) => {
|
|
236
|
+
try {
|
|
237
|
+
const { getDashboard } = await import('../core/dashboard.js');
|
|
238
|
+
const dash = getDashboard();
|
|
239
|
+
const format = req.query.format;
|
|
240
|
+
if (format === 'text') {
|
|
241
|
+
res.type('text/plain').send(dash.getSummary());
|
|
242
|
+
} else {
|
|
243
|
+
res.json(dash.getHealth());
|
|
244
|
+
}
|
|
245
|
+
} catch (e) {
|
|
246
|
+
res.status(500).json({ error: e.message });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
137
250
|
// Frontend Serve
|
|
138
251
|
app.get('*', (req, res) => {
|
|
139
252
|
if (req.path.startsWith('/api')) return res.status(404).json({ error: 'Not Found' });
|