diff --git a/controller/handleOrchestration.js b/controller/handleOrchestration.js index 46b5555..810d852 100644 --- a/controller/handleOrchestration.js +++ b/controller/handleOrchestration.js @@ -14,9 +14,6 @@ const client = new OpenAI({ defaultQuery: { 'api-version': apiVersion } }); - - - const WREN_URL = "http://172.236.172.26:3000/api/graphql"; const gql = async (operationName, query, variables) => { @@ -61,8 +58,6 @@ const pollUntilFinished = async (taskId, maxAttempts = 50) => { throw new Error("Wren polling timeout"); }; - - const fetchWrenData = async (prompt) => { try { // Step 1: Create task @@ -122,7 +117,7 @@ const fetchWrenData = async (prompt) => { Object.fromEntries(columns.map((col, i) => [col, row[i]])) ); - + console.log(`Done — ${rows.length} rows`); console.table(rows); @@ -149,116 +144,6 @@ const fetchWrenData = async (prompt) => { } }; -const generateVegaSchema = async (question, dataArray) => { - try { - const systemPrompt = `You are a data visualization expert. I will provide a user's question and a JSON array of data. Your task is to generate a strictly valid Vega-Lite JSON specification to visualize this data. The data array will be provided to the Vega spec internally. Map the JSON keys to the correct x, y, and color axes. Choose the best chart type (bar, line, arc) based on the question.`; - - - - const userPrompt = `User Question: "${question}"\nData JSON: ${JSON.stringify(dataArray)}`; - const completion = await client.chat.completions.create({ - model: '', - messages: [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: userPrompt } - ], - response_format: { type: "json_object" } - }); - - const rawText = completion.choices[0].message.content.trim(); - return JSON.parse(rawText); - - } catch (error) { - console.error('Azure OpenAI Engine Error:', error.message); - throw new Error('Failed to transform data architecture into valid Vega-Lite spec'); - } -}; - -const generateSQL = async (prompt, mdl) => { - - try { - const mdlObject = yaml.load(mdl); - const systemPrompt = ` -You are an expert SQL query generator. - -STRICT RULES: -1. Generate ONLY SQL query. -2. No explanation. -3. No markdown. -4. Use ONLY columns from MDL. -5. Use table name correctly. -6. Respect expression fields. -7. Use proper aggregation and GROUP BY. -`; - const userPrompt = `USER QUESTION:${prompt}MDL:${JSON.stringify(mdlObject, null, 2)}`; - const completion = - await client.chat.completions.create({ - model: '', - temperature: 0, - messages: [ - { - role: 'system', - content: systemPrompt - }, - { - role: 'user', - content: userPrompt - } - ] - - }); - const sql = - completion - .choices[0] - .message - .content - .trim(); - return { prompt, sql }; - - } catch (error) { - - - console.error( - 'Generate SQL Error:', - error.message - ); - - throw error; - - } -} - -const handleOrchestration = async (req, res) => { - try { - const { prompt, mdl } = req.body; - const tenant_id = req.user ? req.user.client_id : null; - - if (!prompt) { - return res.status(400).json({ error: 'Prompt field is required in request body' }); - } - if (!tenant_id) { - return res.status(400).json({ error: 'Tenant context (client_id) missing from auth token' }); - } - const promptwithsql = await generateSQL(prompt, mdl) - // console.log('Prompt with SQL:', promptwithsql.prompt, promptwithsql.sql, tenant_id); - // return res.status(200).json(promptwithsql); - - const wrenData = await fetchWrenData(promptwithsql.prompt, promptwithsql.sql, tenant_id); - console.log('Wren Data =>', wrenData); - return res.status(200).json(wrenData); - const vegaSchema = await generateVegaSchema(wrenData.question, wrenData.data); - - if (!vegaSchema || !vegaSchema.$schema) { - return res.status(500).json({ error: 'Egress Pipeline Validation Failure: Output missing standard Vega $schema identifier' }); - } - - return res.status(200).json(vegaSchema); - - } catch (error) { - console.error('API Gateway Orchestrator Crash:', error.message); - return res.status(500).json({ error: error.message }); - } -}; const generateVegaJson = async (queryResult) => { try { @@ -287,13 +172,13 @@ Generate Vega-Lite JSON. }); const vegaJson = completion.choices[0].message.content.trim(); -const cleanJson = vegaJson - .replace(/```json/g, "") - .replace(/```/g, "") - .trim(); + const cleanJson = vegaJson + .replace(/```json/g, "") + .replace(/```/g, "") + .trim(); + + return JSON.parse(cleanJson); -return JSON.parse(cleanJson); - } catch (err) { console.error("Vega Generation Error =>", err.message); @@ -312,14 +197,16 @@ const ask = async (req, res) => { } const result = await fetchWrenData(prompt); - + if (!result.success) { + return res.json(result); + } const vegaSpec = await generateVegaJson({ columns: result.columns, data: result.data, chart: result.prompt, sql: result.sql }); -// console.log("Ask Result =>",vegaSpec); + // console.log("Ask Result =>",vegaSpec); return res.json({ ...result, vegaSpec @@ -334,22 +221,12 @@ const ask = async (req, res) => { }); } }; -// const ask = async (req, res) => { -// const { prompt } = req.body; -// if (!prompt?.trim()) { -// return res.status(400).json({ success: false, error: "Prompt required" }); -// } -// const result = await fetchWrenData(prompt); -// console.log('Ask Result =>', result); - -// res.json(result); -// } const getSuggestedQuestions = async () => { - try { - const { threads } = await gql( - "Threads", - ` + try { + const { threads } = await gql( + "Threads", + ` query Threads { threads { id @@ -357,165 +234,365 @@ const getSuggestedQuestions = async () => { } } `, - {} - ); + {} + ); - return (threads || []) - .filter(t => t.summary) - .slice(0, 5) - .map(t => ({ - question: t.summary, - category: "Recent" - })); + return (threads || []) + .filter(t => t.summary) + .slice(0, 5) + .map(t => ({ + question: t.summary, + category: "Recent" + })); - } catch (fallbackErr) { - console.error( - "Fallback failed =>", - fallbackErr.message - ); - return []; - } + } catch (fallbackErr) { + console.error( + "Fallback failed =>", + fallbackErr.message + ); + return []; + } }; const suggestions = async (req, res) => { - const questions = await getSuggestedQuestions(); + const questions = await getSuggestedQuestions(); res.json({ success: true, questions }); } -module.exports = { handleOrchestration,ask,suggestions }; +module.exports = { ask, suggestions }; + + + + + + + + + + + // const axios = require('axios'); -// const dotenv = require('dotenv'); -// dotenv.config(); -// const { GoogleGenerativeAI } = require('@google/generative-ai'); -// const ai = new GoogleGenerativeAI({ apiKey: process.env.GOOGLE_API_KEY }); -// console.log('Gemini API Key Loaded:', process.env.GOOGLE_API_KEY); +// require('dotenv').config(); +// const { OpenAI } = require('openai'); +// const azureEndpoint = "https://cpmindiayoda-resource.services.ai.azure.com"; +// const deploymentName = "gpt-4o-mini"; +// const apiVersion = "2024-08-01-preview"; +// const client = new OpenAI({ +// baseURL: `${azureEndpoint}/openai/deployments/${deploymentName}`, +// apiKey: process.env.AZURE_OPENAI_KEY, +// defaultHeaders: { 'api-key': process.env.AZURE_OPENAI_KEY }, +// defaultQuery: { 'api-version': apiVersion } +// }); +// const WREN_URL = "http://172.236.172.26:3000/api/graphql"; -// const fetchWrenData = async (prompt, tenantId) => { -// try { - -// return { -// question: "Top sales person this month", - -// sql: ` -// SELECT sales_person, -// SUM(amount) AS total_sales -// FROM orders -// WHERE MONTH(created_at)=MONTH(CURRENT_DATE) -// GROUP BY sales_person -// ORDER BY total_sales DESC -// LIMIT 10; -// `, - -// data: [ -// { -// sales_person: "Rahul", -// total_sales: 92000 -// }, -// { -// sales_person: "Amit", -// total_sales: 87000 -// } -// ] -// }; - -// } catch (error) { - -// console.error('Wren AI Integration Error:', error.message); - -// throw new Error('Failed to fetch data payload from Wren AI endpoint'); -// } +// const gql = async (operationName, query, variables) => { +// const res = await axios.post(WREN_URL, { operationName, query, variables }, { +// headers: { "Content-Type": "application/json", Accept: "application/json" }, +// timeout: 60000, +// }); +// if (res.data?.errors) throw new Error(res.data.errors[0].message); +// return res.data.data; // }; -// const generateVegaSchema = async (question, dataArray) => { -// try { -// const model = ai.getGenerativeModel({ -// model: 'gemini-2.5-flash', -// generationConfig: { responseMimeType: 'application/json' } -// }); - -// const systemPrompt = `You are a data visualization expert. I will provide a user's question and a JSON array of data. -// Your task is to generate a strictly valid Vega-Lite JSON specification to visualize this data. -// The data array will be provided to the Vega spec internally. Map the JSON keys to the correct x, y, and color axes. -// Choose the best chart type (bar, line, arc) based on the question.`; - -// const userPrompt = `User Question: "${question}"\nData JSON: ${JSON.stringify(dataArray)}`; - -// const result = await model.generateContent({ -// contents: [{ role: 'user', parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] }] -// }); - -// return JSON.parse(result.response.text()); -// } catch (error) { -// console.error('Gemini Engine Error:', error.message); -// throw new Error('Failed to transform data architecture into valid Vega-Lite spec'); -// } -// }; - - -// const handleOrchestration = async (req, res) => { -// try { -// const { prompt } = req.body; - - -// const tenant_id = req.user ? req.user.client_id : null; -// // return res.status(200).json("ok"); - -// if (!prompt) { -// return res.status(400).json({ error: 'Prompt field is required in request body' }); -// } -// if (!tenant_id) { -// return res.status(400).json({ error: 'Tenant context (client_id) missing from auth token' }); +// const pollUntilFinished = async (taskId, maxAttempts = 50) => { +// for (let i = 0; i < maxAttempts; i++) { +// const { askingTask } = await gql("AskingTask", +// `query AskingTask($taskId: String!) { +// askingTask(taskId: $taskId) { +// status +// candidates { sql } +// error { message } +// queryId // } +// }`, +// { taskId } +// ); +// console.log(`Poll ${i + 1} => ${askingTask?.status}`); +// if (askingTask?.error) throw new Error(askingTask.error.message); -// const wrenData = await fetchWrenData(prompt, tenant_id); +// if (askingTask?.status === "FINISHED") { +// if (askingTask?.candidates?.length > 0) { +// return { sql: askingTask.candidates[0].sql, type: "sql" }; +// } - -// const vegaSchema = await generateVegaSchema(prompt, wrenData); - - -// if (!vegaSchema || !vegaSchema.$schema) { -// return res.status(500).json({ error: 'Egress Pipeline Validation Failure: Output missing standard Vega $schema identifier' }); -// } - - -// return res.status(200).json(vegaSchema); - -// } catch (error) { -// console.error('API Gateway Orchestrator Crash:', error.message); -// return res.status(500).json({ error: error.message }); -// } -// }; - -// module.exports = { handleOrchestration }; - - - -// const fetchWrenData = async (prompt, tenantId) => { -// try { -// const url = process.env.WREN_AI_URL || 'http://localhost:5555/api/v1/text-to-sql'; - -// const response = await axios.post(url, -// { prompt: prompt }, -// { -// headers: { - -// 'X-Wren-Session-Properties': `@user_org_id=${tenantId}`, -// 'Content-Type': 'application/json' -// } -// } +// console.log("No candidates — fetching recommended questions..."); +// try { +// const { createInstantRecommendedQuestions } = await gql( +// "CreateInstantRecommendedQuestions", +// `mutation CreateInstantRecommendedQuestions($data: InstantRecommendedQuestionsInput!) { +// createInstantRecommendedQuestions(data: $data) { id } +// }`, +// { data: { askingTaskId: taskId } } // ); -// return response.data; -// } catch (error) { -// console.error('Wren AI Integration Error:', error.message); -// throw new Error('Failed to fetch data payload from Wren AI endpoint'); + +// const recTaskId = createInstantRecommendedQuestions.id; +// console.log("Rec task ID =>", recTaskId); + +// for (let j = 0; j < 20; j++) { +// const { instantRecommendedQuestions } = await gql( +// "InstantRecommendedQuestions", +// `query InstantRecommendedQuestions($taskId: String!) { +// instantRecommendedQuestions(taskId: $taskId) { +// status +// questions { question category sql } +// error { message } +// } +// }`, +// { taskId: recTaskId } +// ); + +// console.log(`Rec poll ${j + 1} => ${instantRecommendedQuestions?.status}`); + +// if (instantRecommendedQuestions?.status === "FINISHED") { +// return { type: "recommended", questions: instantRecommendedQuestions.questions || [] }; +// } +// if (instantRecommendedQuestions?.error) { +// throw new Error(instantRecommendedQuestions.error.message); +// } +// await new Promise(r => setTimeout(r, 2000)); +// } +// return { type: "recommended", questions: [] }; + +// } catch (recErr) { +// console.error("Recommended questions error =>", recErr.message); +// return { type: "clarification", message: "Please try rephrasing your question." }; +// } // } -// }; \ No newline at end of file + +// await new Promise(r => setTimeout(r, 2000)); +// } +// throw new Error("Wren polling timeout"); +// }; + +// const THREAD_RESPONSE_QUERY = ` +// query ThreadResponse($responseId: Int!) { +// threadResponse(responseId: $responseId) { +// id +// question +// sql +// answerDetail { +// status +// content +// numRowsUsedInLLM +// error { message } +// } +// } +// }`; + +// const pollThreadResponse = async (responseId, maxAttempts = 30) => { +// for (let i = 0; i < maxAttempts; i++) { +// const { threadResponse } = await gql( +// "ThreadResponse", +// THREAD_RESPONSE_QUERY, +// { responseId: parseInt(responseId) } +// ); + +// const status = threadResponse?.answerDetail?.status; +// console.log(`Answer poll ${i + 1} => ${status}`); + +// if (status === "FINISHED" && threadResponse?.answerDetail?.content) { +// return { answer: threadResponse.answerDetail.content }; +// } +// if (status === "FAILED" || threadResponse?.answerDetail?.error) { +// return { answer: null }; +// } +// await new Promise(r => setTimeout(r, 2000)); +// } +// return { answer: null }; +// }; + +// // ✅ FIXED generateVegaJson — JSON parse error fix +// const generateVegaJson = async (queryResult) => { +// try { +// const completion = await client.chat.completions.create({ +// model: "", +// temperature: 0, +// messages: [ +// { +// role: "system", +// content: `You are a data visualization expert. +// Return ONLY a raw JSON object. +// No markdown. No backticks. No explanation. No text before or after. +// Generate a valid Vega-Lite v5 spec with data embedded under "values" key inside "data". +// Choose best chart type (bar, line, arc) based on the question.` +// }, +// { +// role: "user", +// content: `DATA:\n${JSON.stringify(queryResult, null, 2)}\n\nReturn only the Vega-Lite JSON object.` +// } +// ] +// }); + +// let raw = completion.choices[0].message.content.trim(); +// console.log("GPT RAW (first 300 chars) =>", raw.substring(0, 300)); + +// // Remove all markdown fences +// raw = raw +// .replace(/^```json\s*/i, "") +// .replace(/^```vega-lite\s*/i, "") +// .replace(/^```\s*/i, "") +// .replace(/```[\s\S]*$/i, "") +// .trim(); + +// // Extract only { ... } block +// const start = raw.indexOf("{"); +// const end = raw.lastIndexOf("}"); + +// if (start === -1 || end === -1) { +// throw new Error("No JSON object found in GPT response"); +// } + +// const cleanJson = raw.substring(start, end + 1); +// return JSON.parse(cleanJson); + +// } catch (err) { +// console.error("Vega Generation Error =>", err.message); +// // Fallback — basic chart taaki crash na ho +// return { +// "$schema": "https://vega.github.io/schema/vega-lite/v5.json", +// "data": { "values": queryResult.data || [] }, +// "mark": "bar", +// "encoding": { +// "x": { "field": queryResult.columns?.[0] || "x", "type": "nominal", "title": queryResult.columns?.[0] || "X" }, +// "y": { "field": queryResult.columns?.[1] || "y", "type": "quantitative", "title": queryResult.columns?.[1] || "Y" } +// }, +// "width": "container", +// "height": 400 +// }; +// } +// }; + +// const ask = async (req, res) => { +// try { +// const { prompt } = req.body; + +// if (!prompt?.trim()) { +// return res.status(400).json({ success: false, error: "Prompt required" }); +// } + +// // SSE headers +// res.setHeader("Content-Type", "text/event-stream"); +// res.setHeader("Cache-Control", "no-cache"); +// res.setHeader("Connection", "keep-alive"); +// res.flushHeaders(); + +// const send = (event, data) => { +// res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); +// }; + +// // Step 1: Task create karo +// send("status", { step: 1, message: "Generating SQL..." }); +// const { createAskingTask } = await gql("CreateAskingTask", +// `mutation CreateAskingTask($data: AskingTaskInput!) { +// createAskingTask(data: $data) { id } +// }`, +// { data: { question: prompt } } +// ); + +// // Step 2: SQL ready hone tak poll karo +// const pollResult = await pollUntilFinished(createAskingTask.id); + +// // Recommended questions aaye +// if (pollResult.type === "recommended") { +// send("recommended", { questions: pollResult.questions }); +// send("done", { success: true }); +// return res.end(); +// } + +// // Clarification chahiye +// if (pollResult.type === "clarification") { +// send("clarification", { message: pollResult.message }); +// send("done", { success: false }); +// return res.end(); +// } + +// const wrenSql = pollResult.sql; +// send("status", { step: 2, message: "SQL ready, preparing answer..." }); + +// // Step 3: Thread banao +// const { createThread } = await gql("CreateThread", +// `mutation CreateThread($data: CreateThreadInput!) { +// createThread(data: $data) { id } +// }`, +// { data: { question: prompt, sql: wrenSql } } +// ); + +// // Step 4: Thread response banao +// const { createThreadResponse } = await gql("CreateThreadResponse", +// `mutation CreateThreadResponse($threadId: Int!, $data: CreateThreadResponseInput!) { +// createThreadResponse(threadId: $threadId, data: $data) { id } +// }`, +// { threadId: createThread.id, data: { question: prompt, sql: wrenSql } } +// ); + +// const responseId = createThreadResponse.id; +// send("status", { step: 3, message: "Fetching results..." }); + +// // Step 5: Answer + Data parallel fetch karo +// const answerPromise = pollThreadResponse(responseId); + +// const { previewData } = await gql("PreviewData", +// `mutation PreviewData($where: PreviewDataInput!) { +// previewData(where: $where) +// }`, +// { where: { responseId: parseInt(responseId) } } +// ); + +// const columns = previewData.columns.map(c => c.name); +// const rows = previewData.data.map(row => +// Object.fromEntries(columns.map((col, i) => [col, row[i]])) +// ); + +// // Answer pehle bhejo — jaise Wren AI karta hai +// const { answer } = await answerPromise; +// send("answer", { answer: answer || "Data is ready.", sql: wrenSql }); + +// // Table data bhejo +// send("data", { columns, rows, totalRows: rows.length, sql: wrenSql }); + +// // Chart generate karo +// send("status", { step: 4, message: "Generating chart..." }); +// const vegaSpec = await generateVegaJson({ columns, data: rows, chart: prompt, sql: wrenSql }); +// send("chart", { vegaSpec }); + +// send("done", { success: true }); +// res.end(); + +// } catch (err) { +// console.error("Ask Error =>", err.message); +// res.write(`event: error\ndata: ${JSON.stringify({ error: err.message })}\n\n`); +// res.end(); +// } +// }; + +// const getSuggestedQuestions = async () => { +// try { +// const { threads } = await gql("Threads", +// `query Threads { threads { id summary } }`, +// {} +// ); +// return (threads || []) +// .filter(t => t.summary) +// .slice(0, 5) +// .map(t => ({ question: t.summary, category: "Recent" })); +// } catch (err) { +// console.error("Suggestions failed =>", err.message); +// return []; +// } +// }; + +// const suggestions = async (req, res) => { +// const questions = await getSuggestedQuestions(); +// res.json({ success: true, questions }); +// }; + +// module.exports = { ask, suggestions }; \ No newline at end of file diff --git a/route/route.js b/route/route.js index affd2fb..592058f 100644 --- a/route/route.js +++ b/route/route.js @@ -5,7 +5,7 @@ const authMiddleware = require('../middleware/authMiddleware.js'); const handleOrchestration = require('../controller/handleOrchestration.js'); router.post('/loginUser', loginUser.loginUser); -router.post('/handleOrchestration', authMiddleware, handleOrchestration.handleOrchestration); +// router.post('/handleOrchestration', authMiddleware, handleOrchestration.handleOrchestration); router.post('/ask', handleOrchestration.ask); router.get('/suggestions', handleOrchestration.suggestions);