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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vantuz",
3
- "version": "3.3.6",
3
+ "version": "3.3.7",
4
4
  "description": "Yapay Zeka Destekli E-Ticaret Yönetim Platformu - 7 Pazaryeri + WhatsApp/Telegram",
5
5
  "type": "module",
6
6
  "main": "cli.js",
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' });