File size: 7,828 Bytes
7e92e00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/**
 * Smart Text Preprocessor & Intent Router (Claude-Style Pipeline)
 * 
 * NO hardcoded word replacement dictionaries.
 * 
 * Features:
 * 1. Safe Unicode sanitization (stripping zero-width spaces, invisible control characters).
 * 2. Intent Routing (the "IF-uri" inspired by Claude's harness: Coding, Factual, Reasoning, Chat).
 * 3. Language Detection (Romanian vs English contextual detection).
 * 4. Neural Coprocessor Hook: Supports delegating prompt refinement and query rewriting
 *    to a secondary neural model (e.g., local GEC/SLM sidecar or fast LLM endpoint).
 */

export interface PreprocessedInput {
  rawInput: string;
  cleanedText: string;
  searchQuery: string;
  intent: "greeting" | "code" | "reasoning" | "factual_search" | "creative" | "general";
  language: "ro" | "en";
  suggestedStartMsgPrompt?: string;
}

/**
 * Normalizes unicode, invisible characters, and whitespace safely
 * without altering user words or colloquial phrasing.
 */
export function sanitizeText(text: string): string {
  if (!text) return "";

  return text
    // Strip zero-width spaces, invisible characters and directional marks
    .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200D\uFEFF]/g, "")
    // Normalize abnormal unicode space characters to standard space
    .replace(/[\u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]/g, " ")
    // Normalize curly quotes into clean ASCII quotes
    .replace(/[“”„‟]/g, '"')
    .replace(/[‘’‚‛`]/g, "'")
    // Collapse excessive whitespace
    .replace(/[ \t]+/g, " ")
    .replace(/\n{3,}/g, "\n\n")
    .trim();
}

/**
 * Contextual language detection (Romanian vs English) based on function words.
 */
export function detectLanguage(text: string): "ro" | "en" {
  const lower = text.toLowerCase();
  const roMarkers = /\b(cum|ce|de|la|pe|in|și|si|sau|este|sunt|nu|mai|care|pentru|despre|vreau|pot|face|bine|mersi|salut|bună|buna|mulțumesc|multumesc)\b/i;
  const enMarkers = /\b(the|is|are|and|or|not|can|what|how|why|who|where|when|this|that|with|from|have|has|would|could)\b/i;

  const roMatches = (lower.match(roMarkers) || []).length;
  const enMatches = (lower.match(enMarkers) || []).length;

  if (roMatches > enMatches) return "ro";
  if (enMatches > roMatches) return "en";
  if (/[ăâîșț]/i.test(text)) return "ro";
  return "en";
}

/**
 * Extracts a focused search query for RAG and Web Search
 * by stripping conversational filler prefixes if present.
 */
export function extractCleanSearchQuery(text: string): string {
  let q = sanitizeText(text);

  // Strip conversational intro wrappers without altering the core topic
  const prefixRegex = /^(hei|salut|buna|te rog|poti sa|poti|cauta|cauta pe net|cauta pe google|spune-mi despre|can you tell me about|search for|look up|tell me about)\s+/i;
  q = q.replace(prefixRegex, "");
  q = q.replace(/[?!.]+$/, "").trim();

  return q.length > 2 ? q : sanitizeText(text);
}

/**
 * Claude-Style Intent Routing ("IF-urile"):
 * Analyzes the user's intent to dynamically select the best framing and tool activation.
 */
export function classifyIntent(text: string, language: "ro" | "en"): {
  intent: PreprocessedInput["intent"];
  suggestedStartMsgPrompt?: string;
} {
  const lower = text.toLowerCase();

  // IF 1: Greeting / Short conversational chitchat
  const isGreeting = /^(salut|buna|bună|hei|hey|hello|hi|cf|ce faci|ce mai faci|buna ziua|bună ziua)[.?! ]*$/i.test(lower);
  if (isGreeting) {
    return {
      intent: "greeting",
      suggestedStartMsgPrompt: "You are a friendly, concise assistant. Greet warmly and keep the answer brief.",
    };
  }

  // IF 2: Coding, Software Engineering, Bugs & Syntax
  if (
    /\b(python|javascript|typescript|c\+\+|rust|html|css|sql|function|def |class |error|bug|code|script|api|algoritm|compil|react|next\.?js)\b/i.test(lower)
  ) {
    return {
      intent: "code",
      suggestedStartMsgPrompt: "You are an expert software engineer. Provide direct, clean code and concise explanations without fluff.",
    };
  }

  // IF 3: Step-by-step Reasoning / Logic / Math
  if (
    /\b(de ce|cum se explica|calculeaza|demonstreaza|logica|pas cu pas|rezolva|why|calculate|solve|step by step|proof|math|reasoning)\b/i.test(lower) ||
    /[0-9]+\s*[\+\-\*\/]\s*[0-9]+/.test(lower)
  ) {
    return {
      intent: "reasoning",
      suggestedStartMsgPrompt: "Think step-by-step with structured logical reasoning before providing the final concise answer.",
    };
  }

  // IF 4: Real-time Facts / News / Weather / Prices / Current Events
  if (
    /\b(stiri|știri|cine este|cine a fost|ce vreme|pretul|cât costă|azi|recent|data de azi|news|weather|price|who is|latest|current|today)\b/i.test(lower)
  ) {
    return {
      intent: "factual_search",
      suggestedStartMsgPrompt: "You are a factual assistant. Base your answers strictly on verified evidence and state facts concisely.",
    };
  }

  // IF 5: Storytelling / Creativity
  if (
    /\b(poveste|poezie|creeaza o poveste|imagineaza|gluma|story|poem|write a story|creative|joke)\b/i.test(lower)
  ) {
    return {
      intent: "creative",
      suggestedStartMsgPrompt: "Be engaging, imaginative, and creative while crafting a delightful narrative.",
    };
  }

  return { intent: "general" };
}

/**
 * Main Preprocessing Pipeline: Runs sanitization, language detection,
 * query extraction and intent routing without any hardcoded dictionary swaps.
 */
export function preprocessUserInput(rawInput: string): PreprocessedInput {
  const cleanedText = sanitizeText(rawInput);
  const language = detectLanguage(cleanedText);
  const searchQuery = extractCleanSearchQuery(cleanedText);
  const { intent, suggestedStartMsgPrompt } = classifyIntent(cleanedText, language);

  return {
    rawInput,
    cleanedText,
    searchQuery,
    intent,
    language,
    suggestedStartMsgPrompt,
  };
}

/**
 * Asynchronous Smart Neural Preprocessor:
 * Calls the local GPU sidecar model (/v1/refine) powered by Qwen on GPU
 * to perform zero-hardcoded grammatical error correction and intent extraction.
 * Falls back gracefully to heuristic sanitization if server is unavailable or slow.
 */
export async function refinePromptWithNeuralEngine(rawInput: string): Promise<PreprocessedInput> {
  const base = preprocessUserInput(rawInput);
  
  // Romanian language protection: small English-centric SLMs (like Qwen 0.5B)
  // tend to hallucinate Romanian words (e.g. 'pamantul' -> 'pancake').
  // For Romanian, preserve authentic user input and use accurate semantic intent classification.
  if (base.language === "ro") {
    return base;
  }

  const PY_SERVER = process.env.PURCAR_INFERENCE_API_URL || "http://localhost:8000";

  try {
    const ctrl = new AbortController();
    const timer = setTimeout(() => ctrl.abort(), 1500);

    const res = await fetch(`${PY_SERVER}/v1/refine`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ prompt: rawInput }),
      signal: ctrl.signal,
    });
    clearTimeout(timer);

    if (res.ok) {
      const data = await res.json();
      if (data.refined_prompt && data.refined_prompt.trim()) {
        const refinedClean = sanitizeText(data.refined_prompt);
        const lang = detectLanguage(refinedClean);
        const { suggestedStartMsgPrompt } = classifyIntent(refinedClean, lang);

        return {
          rawInput,
          cleanedText: refinedClean,
          searchQuery: data.search_query ? sanitizeText(data.search_query) : base.searchQuery,
          intent: (data.intent as any) || base.intent,
          language: lang,
          suggestedStartMsgPrompt: suggestedStartMsgPrompt || base.suggestedStartMsgPrompt,
        };
      }
    }
  } catch {
    // Graceful fallback to deterministic preprocessor
  }

  return base;
}