From 96a13fb5fe87b46baa5a3a721a30d66706daab9d Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 1 Sep 2025 11:47:25 +0300 Subject: [PATCH 1/2] fix query result --- app/ts/app.ts | 10 -- app/ts/modules/chat.ts | 41 +++--- app/ts/modules/graphs.ts | 8 +- app/ts/modules/messages.ts | 293 +++++++++++++++++++++---------------- 4 files changed, 195 insertions(+), 157 deletions(-) diff --git a/app/ts/app.ts b/app/ts/app.ts index 7a6d8308..e5e3015f 100644 --- a/app/ts/app.ts +++ b/app/ts/app.ts @@ -95,16 +95,6 @@ function setupEventListeners() { } }); - DOM.graphSelectRefresh?.addEventListener('click', () => { - const selected = DOM.graphSelect?.value; - if (!selected) return; - fetch(`/graphs/${encodeURIComponent(selected)}/refresh`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }); - }); DOM.fileUpload?.addEventListener('change', handleFileUpload); window.addEventListener('resize', handleWindowResize); diff --git a/app/ts/modules/chat.ts b/app/ts/modules/chat.ts index 9c4157a9..d398107a 100644 --- a/app/ts/modules/chat.ts +++ b/app/ts/modules/chat.ts @@ -11,7 +11,7 @@ export async function sendMessage() { const selectedValue = DOM.graphSelect?.value || ''; if (!selectedValue) { - addMessage('Please select a graph from the dropdown before sending a message.', false, true); + addMessage('Please select a graph from the dropdown before sending a message.', "followup"); return; } @@ -19,7 +19,7 @@ export async function sendMessage() { state.currentRequestController.abort(); } - addMessage(message, true, false, false, false, (window as any).currentUser || null); + addMessage(message, "user", (window as any).currentUser || null); if (DOM.messageInput) DOM.messageInput.value = ''; // Show typing indicator @@ -27,7 +27,7 @@ export async function sendMessage() { if (DOM.submitButton) DOM.submitButton.style.display = 'none'; if (DOM.pauseButton) DOM.pauseButton.style.display = 'block'; if (DOM.newChatButton) DOM.newChatButton.disabled = true; - addMessage('', false, false, false, true); + addMessage('', "loading"); [DOM.confValue, DOM.expValue, DOM.missValue, DOM.ambValue].forEach((element) => { if (element) element.innerHTML = ''; @@ -61,7 +61,7 @@ export async function sendMessage() { } else { console.error('Error:', error); resetUIState(); - addMessage('Sorry, there was an error processing your message: ' + (error.message || String(error)), false); + addMessage('Sorry, there was an error processing your message: ' + (error.message || String(error))); } state.currentRequestController = null; } @@ -79,9 +79,9 @@ async function processStreamingResponse(response: Response) { if (buffer.trim()) { try { const step = JSON.parse(buffer); - addMessage(step.message || JSON.stringify(step), false); + addMessage(step.message || JSON.stringify(step)); } catch { - addMessage(buffer, false); + addMessage(buffer); } } break; @@ -101,7 +101,7 @@ async function processStreamingResponse(response: Response) { const step = JSON.parse(message); handleStreamMessage(step); } catch { - addMessage('Failed: ' + message, false); + addMessage('Failed: ' + message); } } } @@ -109,7 +109,7 @@ async function processStreamingResponse(response: Response) { function handleStreamMessage(step: any) { if (step.type === 'reasoning_step') { - addMessage(step.message, false); + addMessage(step.message); moveLoadingMessageToBottom(); } else if (step.type === 'final_result') { handleFinalResult(step); @@ -118,13 +118,13 @@ function handleStreamMessage(step: any) { } else if (step.type === 'query_result') { handleQueryResult(step); } else if (step.type === 'ai_response') { - addMessage(step.message, false, false, true); + addMessage(step.message, "final-result"); } else if (step.type === 'destructive_confirmation') { addDestructiveConfirmationMessage(step); } else if (step.type === 'operation_cancelled') { - addMessage(step.message, false, true); + addMessage(step.message, "followup"); } else { - addMessage(step.message || JSON.stringify(step), false); + addMessage(step.message || JSON.stringify(step)); } if (step.type !== 'reasoning_step') { @@ -160,9 +160,9 @@ function handleFinalResult(step: any) { const message = step.message || JSON.stringify(step.data, null, 2); if (step.is_valid) { - addMessage(message, false, false, true); + addMessage(message, "final-result"); } else { - addMessage("Sorry, we couldn't generate a valid SQL query. Please try rephrasing your question or add more details. For help, check the explanation window.", false, true); + addMessage("Sorry, we couldn't generate a valid SQL query. Please try rephrasing your question or add more details. For help, check the explanation window.", "followup"); } } @@ -171,14 +171,15 @@ function handleFollowupQuestions(step: any) { if (DOM.confValue) DOM.confValue.textContent = 'N/A'; if (DOM.missValue) DOM.missValue.textContent = 'N/A'; if (DOM.ambValue) DOM.ambValue.textContent = 'N/A'; - addMessage(step.message, false, true); + addMessage(step.message, "followup"); } function handleQueryResult(step: any) { if (step.data) { - addMessage(`Query Result: ${JSON.stringify(step.data)}`, false, false, true); + console.log(step.data); + addMessage("Query Result", "query-final-result", null, step.data); } else { - addMessage('No results found for the query.', false); + addMessage('No results found for the query.'); } } @@ -196,7 +197,7 @@ export function pauseRequest() { state.currentRequestController = null; resetUIState(); - addMessage('Request was paused by user.', false, true); + addMessage('Request was paused by user.', "followup"); } } @@ -249,10 +250,10 @@ export async function handleDestructiveConfirmation(confirmation: string, sqlQue if (DOM.messageInput) DOM.messageInput.disabled = false; if (DOM.submitButton) DOM.submitButton.disabled = false; - addMessage(`User choice: ${confirmation}`, true, false, false, false, (window as any).currentUser || null); + addMessage(`User choice: ${confirmation}`, "user", (window as any).currentUser || null); if (confirmation === 'CANCEL') { - addMessage('Operation cancelled. The destructive SQL query was not executed.', false, true); + addMessage('Operation cancelled. The destructive SQL query was not executed.', "followup"); return; } @@ -276,7 +277,7 @@ export async function handleDestructiveConfirmation(confirmation: string, sqlQue await processStreamingResponse(response); } catch (error: any) { console.error('Error:', error); - addMessage('Sorry, there was an error processing the confirmation: ' + (error.message || String(error)), false); + addMessage('Sorry, there was an error processing the confirmation: ' + (error.message || String(error))); } } diff --git a/app/ts/modules/graphs.ts b/app/ts/modules/graphs.ts index 4eb2cbe8..4b19a3d3 100644 --- a/app/ts/modules/graphs.ts +++ b/app/ts/modules/graphs.ts @@ -46,7 +46,7 @@ export function loadGraphs() { if (DOM.submitButton) DOM.submitButton.disabled = true; if (DOM.messageInput) DOM.messageInput.placeholder = 'Please upload a schema or connect a database to start chatting'; - addMessage('No graphs are available. Please upload a schema file or connect to a database to get started.', false); + addMessage('No graphs are available. Please upload a schema file or connect to a database to get started.'); return; } @@ -66,9 +66,9 @@ export function loadGraphs() { console.error('Error fetching graphs:', error); if ((error as Error).message.includes('Authentication required')) { - addMessage('Authentication required. Please log in to access your graphs.', false); + addMessage('Authentication required. Please log in to access your graphs.'); } else { - addMessage('Sorry, there was an error fetching the available graphs: ' + (error as Error).message, false); + addMessage('Sorry, there was an error fetching the available graphs: ' + (error as Error).message); if (DOM.messageInput) DOM.messageInput.disabled = true; if (DOM.submitButton) DOM.submitButton.disabled = true; if (DOM.messageInput) DOM.messageInput.placeholder = 'Cannot connect to server'; @@ -99,7 +99,7 @@ export function handleFileUpload(event: Event) { console.log('File uploaded successfully', data); }).catch(error => { console.error('Error uploading file:', error); - addMessage('Sorry, there was an error uploading your file: ' + (error as Error).message, false); + addMessage('Sorry, there was an error uploading your file: ' + (error as Error).message); }); } diff --git a/app/ts/modules/messages.ts b/app/ts/modules/messages.ts index 6f26c5a0..ae374958 100644 --- a/app/ts/modules/messages.ts +++ b/app/ts/modules/messages.ts @@ -2,147 +2,194 @@ * Message handling and UI functions (TypeScript) */ -import { DOM, state } from './config'; +import { DOM, state } from "./config"; export function addMessage( - message: string, - isUser = false, - isFollowup = false, - isFinalResult = false, - isLoading = false, - userInfo: { picture?: string; name?: string } | null = null + message: string, + type: + | "user" + | "bot" + | "followup" + | "final-result" + | "query-final-result" + | "loading" = "bot", + userInfo: { picture?: string; name?: string } | null = null, + queryResult: any = null ) { - const messageDiv = document.createElement('div'); - const messageDivContainer = document.createElement('div'); - - messageDiv.className = 'message'; - messageDivContainer.className = 'message-container'; - - let userAvatar: HTMLImageElement | null = null; - - if (isFollowup) { - messageDivContainer.className += ' followup-message-container'; - messageDiv.className += ' followup-message'; - messageDiv.textContent = message; - } else if (isUser) { - messageDivContainer.className += ' user-message-container'; - messageDiv.className += ' user-message'; - - if (userInfo && userInfo.picture) { - userAvatar = document.createElement('img'); - userAvatar.src = userInfo.picture; - userAvatar.alt = (userInfo.name?.charAt(0).toUpperCase() as string) || 'User'; - userAvatar.className = 'user-message-avatar'; - messageDivContainer.classList.add('has-avatar'); - } - - state.questions_history.push(message); - } else if (isFinalResult) { - state.result_history.push(message); - messageDivContainer.className += ' final-result-message-container'; - messageDiv.className += ' final-result-message'; - } else { - messageDivContainer.className += ' bot-message-container'; - messageDiv.className += ' bot-message'; - if (isLoading) { - messageDivContainer.id = 'loading-message-container'; - messageDivContainer.className += ' loading-message-container'; - } - } - - const block = formatBlock(message); - - if (block) { - block.forEach((lineDiv) => { - messageDiv.appendChild(lineDiv); + const messageDiv = document.createElement("div"); + const messageDivContainer = document.createElement("div"); + + messageDiv.className = "message"; + messageDivContainer.className = "message-container"; + + let userAvatar: HTMLImageElement | null = null; + + switch (type) { + case "followup": + messageDivContainer.className += " followup-message-container"; + messageDiv.className += " followup-message"; + messageDiv.textContent = message; + break; + case "user": + messageDivContainer.className += " user-message-container"; + messageDiv.className += " user-message"; + + if (userInfo && userInfo.picture) { + userAvatar = document.createElement("img"); + userAvatar.src = userInfo.picture; + userAvatar.alt = + (userInfo.name?.charAt(0).toUpperCase() as string) || "User"; + userAvatar.className = "user-message-avatar"; + messageDivContainer.classList.add("has-avatar"); + } + + state.questions_history.push(message); + break; + case "bot": + messageDivContainer.className += " bot-message-container"; + messageDiv.className += " bot-message"; + break; + case "final-result": + state.result_history.push(message); + messageDivContainer.className += " final-result-message-container"; + messageDiv.className += " final-result-message"; + break; + case "query-final-result": + messageDivContainer.className += " final-result-message-container"; + messageDiv.className += " final-result-message"; + messageDiv.style.overflow = "auto"; + const table = document.createElement("table"); + const tableHeader = document.createElement("thead"); + const headerRow = document.createElement("tr"); + Object.keys(queryResult[0]).forEach((column: any) => { + const headerCell = document.createElement("th"); + headerCell.textContent = column; + headerRow.appendChild(headerCell); + }); + tableHeader.appendChild(headerRow); + table.appendChild(tableHeader); + const tableBody = document.createElement("tbody"); + queryResult.forEach((row: any, index: number) => { + const rowRow = document.createElement("tr"); + Object.values(row).forEach((value: any) => { + const cell = document.createElement("td"); + cell.textContent = value; + rowRow.appendChild(cell); }); - } else if (!isLoading) { - messageDiv.textContent = message; - } - - if (!isLoading) { - messageDivContainer.appendChild(messageDiv); - if (userAvatar) { - messageDivContainer.appendChild(userAvatar); - } + tableBody.appendChild(rowRow); + }); + table.appendChild(tableBody); + messageDiv.appendChild(table); + break; + case "loading": + messageDivContainer.className += " bot-message-container"; + messageDiv.className += " bot-message"; + messageDivContainer.id = "loading-message-container"; + messageDivContainer.className += " loading-message-container"; + break; + } + + const block = formatBlock(message); + + if (block) { + block.forEach((lineDiv) => { + messageDiv.appendChild(lineDiv); + }); + } else if (type !== "loading" && type !== "query-final-result") { + messageDiv.textContent = message; + } + + if (type !== "loading") { + messageDivContainer.appendChild(messageDiv); + if (userAvatar) { + messageDivContainer.appendChild(userAvatar); } + } - DOM.chatMessages?.appendChild(messageDivContainer); - if (DOM.chatMessages) DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight; + DOM.chatMessages?.appendChild(messageDivContainer); + if (DOM.chatMessages) + DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight; - return messageDiv; + return messageDiv; } export function removeLoadingMessage() { - const loadingMessageContainer = document.getElementById('loading-message-container'); - if (loadingMessageContainer) { - loadingMessageContainer.remove(); - } + const loadingMessageContainer = document.getElementById( + "loading-message-container" + ); + if (loadingMessageContainer) { + loadingMessageContainer.remove(); + } } export function moveLoadingMessageToBottom() { - const loadingMessageContainer = document.getElementById('loading-message-container'); - if (loadingMessageContainer && DOM.chatMessages) { - loadingMessageContainer.remove(); - DOM.chatMessages.appendChild(loadingMessageContainer); - DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight; - } + const loadingMessageContainer = document.getElementById( + "loading-message-container" + ); + if (loadingMessageContainer && DOM.chatMessages) { + loadingMessageContainer.remove(); + DOM.chatMessages.appendChild(loadingMessageContainer); + DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight; + } } export function formatBlock(text: string) { - text = text.replace(/^"(.*)"$/, '$1').trim(); - - // SQL block - if (text.startsWith('```sql') && text.endsWith('```')) { - const sql = text.slice(6, -3).trim(); - return sql.split('\n').map((line) => { - const lineDiv = document.createElement('div'); - lineDiv.className = 'sql-line'; - lineDiv.textContent = line; - return lineDiv; - }); - } - - // Array block - if (text.includes('[') && text.includes(']')) { - const parts = text.split('['); - const formattedParts = parts.map((part) => { - const lineDiv = document.createElement('div'); - lineDiv.className = 'array-line'; - part = part.replaceAll(']', ''); - lineDiv.textContent = part; - return lineDiv; - }); - return formattedParts; - } - - // Generic multi-line block - text = text.replace(/\\n/g, '\n'); - if (text.includes('\n')) { - return text.split('\n').map((line) => { - const lineDiv = document.createElement('div'); - lineDiv.className = 'plain-line'; - lineDiv.textContent = line; - return lineDiv; - }); - } + text = text.replace(/^"(.*)"$/, "$1").trim(); + + // SQL block + if (text.startsWith("```sql") && text.endsWith("```")) { + const sql = text.slice(6, -3).trim(); + return sql.split("\n").map((line) => { + const lineDiv = document.createElement("div"); + lineDiv.textContent = line; + return lineDiv; + }); + } + + // Array block + if (text.includes("[") && text.includes("]")) { + const parts = text.split("["); + const formattedParts = parts.map((part) => { + const lineDiv = document.createElement("div"); + part = part.replace(/\]/g, ""); + lineDiv.textContent = part; + return lineDiv; + }); + return formattedParts; + } + + // Generic multi-line block + text = text.replace(/\\n/g, "\n"); + if (text.includes("\n")) { + return text.split("\n").map((line) => { + const lineDiv = document.createElement("div"); + lineDiv.textContent = line; + return lineDiv; + }); + } - return null; + return null; } export function initChat() { - if (DOM.messageInput) DOM.messageInput.value = ''; - if (DOM.chatMessages) DOM.chatMessages.innerHTML = ''; - [DOM.confValue, DOM.expValue, DOM.missValue].forEach((element) => { - if (element) element.innerHTML = ''; - }); - - if (DOM.graphSelect && DOM.graphSelect.options.length > 0 && (DOM.graphSelect.options[0].value || DOM.graphSelect.options.length)) { - addMessage('Hello! How can I help you today?', false); - } else { - addMessage('Hello! Please select a graph from the dropdown above, upload a schema or connect to a database to get started.', false); - } - - state.questions_history = []; - state.result_history = []; + if (DOM.messageInput) DOM.messageInput.value = ""; + if (DOM.chatMessages) DOM.chatMessages.innerHTML = ""; + [DOM.confValue, DOM.expValue, DOM.missValue].forEach((element) => { + if (element) element.innerHTML = ""; + }); + + if ( + DOM.graphSelect && + DOM.graphSelect.options.length > 0 && + (DOM.graphSelect.options[0].value || DOM.graphSelect.options.length) + ) { + addMessage("Hello! How can I help you today?"); + } else { + addMessage( + "Hello! Please select a graph from the dropdown above, upload a schema or connect to a database to get started." + ); + } + + state.questions_history = []; + state.result_history = []; } From 4b1d65af297cf84e7634732ab9b772c541c73eb0 Mon Sep 17 00:00:00 2001 From: Anchel123 <110421452+Anchel123@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:01:09 +0300 Subject: [PATCH 2/2] Update .env.example --- .env.example | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 2d440bcb..bf9ed935 100644 --- a/.env.example +++ b/.env.example @@ -74,8 +74,9 @@ FALKORDB_URL=redis://localhost:6379/0 # REQUIRED - change to your FalkorDB URL # GITHUB_CLIENT_ID=your_github_client_id # GITHUB_CLIENT_SECRET=your_github_client_secret -# FalkorDB configuration -FALKORDB_URL=redis://localhost:6379 +# If your OAuth app uses a different base URL than the request base (e.g., using 127.0.0.1 vs localhost) +# you can override the base used for building callback URLs. Example: +# OAUTH_BASE_URL=http://localhost:5000 # ----------------------------- # Email Configuration (optional - for sending invitation emails)