File size: 3,432 Bytes
0ec8c7c
79c5a92
628ec36
0ec8c7c
469b970
e6a627d
f86ee93
46f9a7a
d14861f
e6a627d
 
f86ee93
79c5a92
8db634a
f86ee93
0ec8c7c
 
35d8bd5
d14861f
6a25ae8
d14861f
f86ee93
628ec36
0ec8c7c
 
628ec36
 
 
 
d14861f
0ec8c7c
 
 
628ec36
0ec8c7c
628ec36
 
 
 
d14861f
0ec8c7c
d14861f
0ec8c7c
79c5a92
d897af7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d14861f
0ec8c7c
f86ee93
 
d14861f
 
 
 
 
 
f86ee93
bbb4e4b
 
f86ee93
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/** engine/classifier.js V101.0 - Hybrid Vision Integration */
const { routeWithLLM } = require('../services/llmService');
const { downloadFile } = require('../services/storageService');
const { processImageUpload } = require('./imageProcessor');

const KEYWORDS = {
    reset: ['reset', 'ื”ืชื—ืœ', 'ืื™ืคื•ืก', 'ืจื™ืกื˜', 'ืชืคืจื™ื˜'],
    cart: ['ืขื’ืœื”', 'ืกื™ื›ื•ื', 'ื›ืžื” ื–ื” ื™ื•ืฆื', 'ื›ืžื” ื™ืฆื'],
    checkout: ['ืชืืจื•ื–', 'ื”ืฆืขืช ืžื—ื™ืจ', 'ืœืฉืœื', 'ื—ืฉื‘ื•ืŸ', 'ืฆ\'ืง ืืื•ื˜', 'checkout']
};

async function classify(text, session) {
    const safeText = String(text || "");
    const t = safeText.toLowerCase().trim();

    // 1. Technical Fast Paths
    if (t.startsWith('system_')) return { intent: 'system_action', action: t, raw_text: safeText };
    if (KEYWORDS.reset.some(k => t.includes(k)) && t.split(' ').length <= 4) return { intent: 'reset' };
    if (KEYWORDS.cart.some(k => t.includes(k))) return { intent: 'show_cart' };

    // 2. LLM / Compiler Pipeline
    try {
        let imageBuffer = null;
        let technicalMetadata = null;

        if (safeText.includes('[IMAGE_UPLOADED:')) {
            const match = safeText.match(/\[IMAGE_UPLOADED:\s*([^\]]+)\]/);
            if (match) {
                try {
                    imageBuffer = await downloadFile(match[1].trim());
                    // LAYER 1: The Code is Judge (Deterministic physical measurement)
                    technicalMetadata = await processImageUpload(imageBuffer);
                    session.lastImageMetadata = technicalMetadata; // Persist for context
                } catch (e) {
                    console.error("โŒ [VISION ERROR]:", e.message);
                }
            }
        }

        const extraction = await routeWithLLM(safeText, session, imageBuffer);

        extraction.raw_text = safeText;
        extraction.technicalMetadata = technicalMetadata;

        // --- Spec v5.7.4: Deterministic Semantic Closure Gate ---
        // Calculate closure intent and confirmation flag heuristically
        let closureScore = 0;
        let isConfirmed = false;

        const positiveClosureSignals = ["ื”ืฉืืจ", "ื”ื›ืœ ืžื•ืฉืœื", "ืกื’ื•ืจ", "ืชืŸ ื”ืฆืขื”", "ืžืขื•ืœื”", "ื‘ื“ื™ื•ืง", "ื–ื”ื•", "ืชืชืงื“ื", "ืฉืœื— ื‘ื”ืงื“ื", "ื”ืžืฉืš", "ื‘ืกื“ืจ", "ืžื•ื›ืŸ"];
        const conditionSignals = ["ืื‘ืœ", "ื—ื•ืฅ ืž", "ืื•ืœื™", "ืœืฉื ื•ืช", "ืจื’ืข", "ืฉื ื”", "ืชื—ืœื™ืฃ", "ื‘ืžืงื•ื"];

        const hasPositiveSignal = positiveClosureSignals.some(s => t.includes(s));
        const hasCondition = conditionSignals.some(s => t.includes(s));

        if (hasPositiveSignal && !hasCondition && extraction.intent !== 'update' && extraction.intent !== 'reset') {
            closureScore = 0.9;
            isConfirmed = true;
        } else if (hasCondition || extraction.intent === 'update') {
            closureScore = 0.0;
            isConfirmed = false;
        }

        extraction.closure_intent_score = closureScore;
        extraction.confirmation_flag = isConfirmed;

        return extraction;

    } catch (e) {
        console.error("Classifier Error:", e);
        return {
            intent: "chat",
            answer_text: "ืกืœื™ื—ื”, ืื ื™ ื—ื•ื•ื” ืงื•ืฉื™ ืงื˜ืŸ ื‘ืขื™ื‘ื•ื“. ื ืกื” ืฉื•ื‘?",
            products_detected: [],
            parameters_detected: []
        };
    }
}

module.exports = { classifyMessage: classify };