import { GoogleGenAI } from "@google/genai"; import { AiProvider, KBArticle, Ticket, TicketStatus } from "../types"; // --- OPENROUTER / OPENAI COMPATIBLE FETCH --- async function callOpenRouter(apiKey: string, model: string, messages: any[]) { const response = await fetch("https://openrouter.ai/api/v1/chat/completions", { method: "POST", headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", "HTTP-Referer": window.location.origin, // Required by OpenRouter "X-Title": "OmniSupport AI" // Optional }, body: JSON.stringify({ model: model || "openai/gpt-3.5-turbo", messages: messages }) }); if (!response.ok) { const err = await response.text(); throw new Error(`OpenRouter API Error: ${err}`); } const data = await response.json(); return data.choices[0]?.message?.content || ""; } /** * Agent 1: Customer Support Chat * Uses the KB to answer questions. */ export const getSupportResponse = async ( apiKey: string, userQuery: string, chatHistory: string[], knowledgeBase: KBArticle[], provider: AiProvider = AiProvider.GEMINI, model: string = 'gemini-3-flash-preview', customConfig: { agentName?: string; customPrompt?: string } = {} ): Promise => { if (!apiKey) { return "L'assistente AI non è configurato (API Key mancante). Contatta l'amministratore."; } // Prepare Context from KB const kbContext = knowledgeBase.map(a => { if (a.type === 'url') { return `Fonte Esterna [${a.category}] (${a.visibility}): ${a.title} - URL: ${a.url}\nContenuto Estratto: ${a.content}`; } return `Articolo [${a.category}] (${a.visibility}): ${a.title}\nContenuto: ${a.content}`; }).join('\n\n'); const agentName = customConfig.agentName || "OmniSupport AI"; const extraPrompt = customConfig.customPrompt ? `ISTRUZIONI SPECIFICHE AGGIUNTIVE:\n${customConfig.customPrompt}` : ""; const systemInstructionText = ` Sei "${agentName}", un assistente clienti virtuale. ${extraPrompt} IL TUO COMPITO: Rispondere alle domande dei clienti basandoti ESCLUSIVAMENTE sulla seguente Base di Conoscenza (KB). GESTIONE LINGUA (IMPORTANTE): 1. Rileva automaticamente la lingua utilizzata dall'utente nel suo ultimo messaggio. 2. Rispondi SEMPRE nella lingua dell'utente. BASE DI CONOSCENZA: ${kbContext} REGOLE: 1. Se la risposta è nella KB (che sia 'public' o 'internal'), usala per formulare la risposta. Non menzionare se l'articolo è interno o pubblico, usa solo l'informazione. 2. Se l'articolo è una fonte web (URL), usa il "Contenuto Estratto" per rispondere e fornisci anche il link originale all'utente. 3. Se la risposta NON si trova nella KB, ammettilo gentilmente e consiglia di aprire un ticket. 4. Sii cortese, professionale e sintetico. `; try { if (provider === AiProvider.OPENROUTER || provider === AiProvider.OPENAI || provider === AiProvider.DEEPSEEK) { // Logic for OpenRouter/OpenAI compatible APIs const messages = [ { role: "system", content: systemInstructionText }, ...chatHistory.map(msg => ({ role: "user", content: msg })), { role: "user", content: userQuery } ]; const response = await callOpenRouter(apiKey, model, messages); return response || "Mi dispiace, non riesco a generare una risposta al momento."; } else { // Default to Google Gemini const ai = new GoogleGenAI({ apiKey }); const response = await ai.models.generateContent({ model: model, contents: [ ...chatHistory.map(msg => ({ role: 'user', parts: [{ text: msg }] })), { role: 'user', parts: [{ text: userQuery }] } ], config: { systemInstruction: systemInstructionText, temperature: 0.3, } }); return response.text || "Mi dispiace, non riesco a generare una risposta al momento."; } } catch (error) { console.error("AI Service Error:", error); return "Si è verificato un errore nel servizio AI (Verifica API Key, Provider o connessione)."; } }; /** * Agent 2: Knowledge Extraction * Returns an ARRAY of suggested articles. */ export const generateNewKBArticle = async ( apiKey: string, resolvedTickets: Ticket[], existingArticles: KBArticle[], provider: AiProvider = AiProvider.GEMINI, model: string = 'gemini-3-pro-preview' ): Promise | null> => { if (!apiKey) return null; // Filter only resolved tickets that haven't been analyzed yet const relevantTickets = resolvedTickets.filter(t => t.status === TicketStatus.RESOLVED && !t.hasBeenAnalyzed); if (relevantTickets.length === 0) return null; // Aggregate ticket conversations const transcripts = relevantTickets.map(t => { const convo = t.messages.map(m => `${m.role.toUpperCase()}: ${m.content}`).join('\n'); return `TICKET ID: ${t.id}\nOGGETTO: ${t.subject}\nCONVERSAZIONE:\n${convo}\n---`; }).join('\n'); const existingTitles = existingArticles.map(a => a.title).join(', '); const systemPrompt = ` Sei un Knowledge Manager AI esperto. Analizza TUTTI i seguenti ticket risolti e confrontali con gli articoli esistenti nella Knowledge Base. ARTICOLI ESISTENTI: ${existingTitles} TICKET RISOLTI RECENTI: ${transcripts} OBIETTIVO: 1. Identifica TUTTI i problemi ricorrenti o le soluzioni tecniche presenti nei ticket risolti MA NON coperti dagli articoli esistenti. 2. Per ogni lacuna trovata, scrivi un NUOVO articolo di Knowledge Base per colmarla. 3. Restituisci il risultato ESCLUSIVAMENTE come un ARRAY JSON di oggetti. Non aggiungere markdown code blocks. SCHEMA JSON RICHIESTO (ARRAY): [ { "title": "Titolo del nuovo articolo", "content": "Contenuto dettagliato in formato Markdown (usa elenchi puntati per istruzioni)", "category": "Categoria suggerita" }, ... ] Se non trovi lacune significative, restituisci un array vuoto []. `; try { let rawText = ""; if (provider === AiProvider.OPENROUTER || provider === AiProvider.OPENAI || provider === AiProvider.DEEPSEEK) { const messages = [{ role: "system", content: systemPrompt }]; rawText = await callOpenRouter(apiKey, model, messages); } else { const ai = new GoogleGenAI({ apiKey }); const response = await ai.models.generateContent({ model: model, contents: systemPrompt, config: { responseMimeType: "application/json" } }); rawText = response.text || ""; } if (!rawText) return null; // Clean markdown if present const cleanedText = rawText.replace(/\`\`\`json/g, '').replace(/\`\`\`/g, '').trim(); const result = JSON.parse(cleanedText); if (Array.isArray(result) && result.length > 0) { return result; } return []; } catch (error) { console.error("Knowledge Agent Error:", error); return null; } };