diff --git a/app/public/css/menu.css b/app/public/css/menu.css index 6b3cefdc..431eb4f2 100644 --- a/app/public/css/menu.css +++ b/app/public/css/menu.css @@ -116,6 +116,24 @@ border-color: var(--text-secondary); } +#query-final-result-table { + border-collapse: collapse; +} + +#query-final-result-table th, +#query-final-result-table td { + padding: 4px 8px; +} + +#query-final-result-table th:not(:first-child), +#query-final-result-table td:not(:first-child) { + border-left: 1px solid var(--text-primary); +} + +#query-final-result-table td { + border-top: 1px solid var(--text-primary); +} + #graph-select { height: 100%; padding: 8px 12px; @@ -143,7 +161,7 @@ #schema-content { position: relative; width: 100%; - height: 100%; + height: 100%; } #schema-controls { diff --git a/app/ts/app.ts b/app/ts/app.ts index ae6d937e..aa6eb810 100644 --- a/app/ts/app.ts +++ b/app/ts/app.ts @@ -22,6 +22,7 @@ import { setupAuthenticationModal, setupDatabaseModal } from './modules/modals'; import { resizeGraph, showGraph } from './modules/schema'; import { setupTokenManagement } from './modules/tokens'; import { initLeftToolbar } from './modules/left_toolbar'; +import { resizeGraph } from './modules/schema'; async function loadAndShowGraph(selected: string | undefined) { if (!selected) return; diff --git a/app/ts/modules/chat.ts b/app/ts/modules/chat.ts index d792d0dc..12613222 100644 --- a/app/ts/modules/chat.ts +++ b/app/ts/modules/chat.ts @@ -12,7 +12,7 @@ export async function sendMessage() { const selectedValue = getSelectedGraph() || ''; 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; } @@ -20,7 +20,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 @@ -28,7 +28,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 = ''; @@ -62,7 +62,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; } @@ -80,9 +80,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; @@ -102,7 +102,7 @@ async function processStreamingResponse(response: Response) { const step = JSON.parse(message); handleStreamMessage(step); } catch { - addMessage('Failed: ' + message, false); + addMessage('Failed: ' + message); } } } @@ -115,7 +115,7 @@ 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); @@ -124,13 +124,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') { @@ -166,9 +166,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"); } } @@ -177,14 +177,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.'); } } @@ -202,7 +203,7 @@ export function pauseRequest() { state.currentRequestController = null; resetUIState(); - addMessage('Request was paused by user.', false, true); + addMessage('Request was paused by user.', "followup"); } } @@ -255,10 +256,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; } @@ -282,7 +283,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 96997b3d..8ea021cf 100644 --- a/app/ts/modules/graphs.ts +++ b/app/ts/modules/graphs.ts @@ -2,183 +2,226 @@ * Graph loading and management functionality (TypeScript) */ -import { DOM } from './config'; -import { addMessage, initChat } from './messages'; -import { addGraphOption, clearGraphOptions, setSelectedGraph, toggleOptions, getSelectedGraph } from './graph_select'; +import { DOM } from "./config"; +import { addMessage, initChat } from "./messages"; +import { + addGraphOption, + clearGraphOptions, + setSelectedGraph, + toggleOptions, + getSelectedGraph, +} from "./graph_select"; export function loadGraphs() { - const isAuthenticated = (window as any).isAuthenticated !== undefined ? (window as any).isAuthenticated : false; - - if (!isAuthenticated) { - const option = document.createElement('option'); - option.value = ''; - option.textContent = 'Please log in to access graphs'; - option.disabled = true; + const isAuthenticated = + (window as any).isAuthenticated !== undefined + ? (window as any).isAuthenticated + : false; + + if (!isAuthenticated) { + const option = document.createElement("option"); + option.value = ""; + option.textContent = "Please log in to access graphs"; + option.disabled = true; + + if (DOM.messageInput) DOM.messageInput.disabled = true; + if (DOM.submitButton) DOM.submitButton.disabled = true; + if (DOM.messageInput) + DOM.messageInput.placeholder = "Please log in to start chatting"; + return; + } + + fetch("/graphs") + .then((response) => { + if (!response.ok) { + if (response.status === 401) { + throw new Error( + "Authentication required. Please log in to access graphs." + ); + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + return response.json(); + }) + .then((data: string[]) => { + console.log("Graphs loaded:", data); + if (!data || data.length === 0) { + // Clear custom dropdown and show no graphs state + clearGraphOptions(); if (DOM.messageInput) DOM.messageInput.disabled = true; if (DOM.submitButton) DOM.submitButton.disabled = true; - if (DOM.messageInput) DOM.messageInput.placeholder = 'Please log in to start chatting'; + 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." + ); + + // Update the visible selected label to show no graphs state + const selectedLabel = document.getElementById("graph-selected"); + if (selectedLabel) { + const dropdownText = selectedLabel.querySelector(".dropdown-text"); + if (dropdownText) { + dropdownText.textContent = "No Databases"; + } + } return; - } + } + + // populate hidden select for legacy code + data.forEach((graph) => { + const option = document.createElement("option"); + option.value = graph; + option.textContent = graph; + option.title = graph; + }); - fetch('/graphs') - .then(response => { - if (!response.ok) { - if (response.status === 401) { - throw new Error('Authentication required. Please log in to access graphs.'); - } - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - return response.json(); - }) - .then((data: string[]) => { - console.log('Graphs loaded:', data); - if (!data || data.length === 0) { - // Clear custom dropdown and show no graphs state - clearGraphOptions(); - - if (DOM.messageInput) DOM.messageInput.disabled = true; - 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); - - // Update the visible selected label to show no graphs state - const selectedLabel = document.getElementById('graph-selected'); - if (selectedLabel) { - const dropdownText = selectedLabel.querySelector('.dropdown-text'); - if (dropdownText) { - dropdownText.textContent = 'No Databases'; - } + // populate custom dropdown + try { + clearGraphOptions(); + data.forEach((graph) => { + addGraphOption( + graph, + (name) => { + // onSelect + setSelectedGraph(name); + initChat(); + }, + async (name) => { + // onDelete + const confirmed = confirm( + `Delete graph "${name}"? This action cannot be undone.` + ); + if (!confirmed) return; + try { + const resp = await fetch( + `/graphs/${encodeURIComponent(name)}`, + { method: "DELETE" } + ); + if (!resp.ok) { + const text = await resp.text(); + throw new Error(`Delete failed: ${resp.status} ${text}`); } - return; - } - - // populate hidden select for legacy code - data.forEach(graph => { - const option = document.createElement('option'); - option.value = graph; - option.textContent = graph; - option.title = graph; - }); - - // populate custom dropdown - try { - clearGraphOptions(); - data.forEach(graph => { - addGraphOption(graph, (name) => { - // onSelect - setSelectedGraph(name); - initChat(); - }, async (name) => { - // onDelete - const confirmed = confirm(`Delete graph "${name}"? This action cannot be undone.`); - if (!confirmed) return; - try { - const resp = await fetch(`/graphs/${encodeURIComponent(name)}`, { method: 'DELETE' }); - if (!resp.ok) { - const text = await resp.text(); - throw new Error(`Delete failed: ${resp.status} ${text}`); - } - addMessage(`Graph "${name}" deleted.`, false); - } catch (err) { - console.error('Error deleting graph:', err); - addMessage('Error deleting graph: ' + (err as Error).message, false); - } finally { - // Always refresh the graph list after delete attempt - loadGraphs(); - } - }); - }); - - const sel = document.getElementById('graph-selected'); - if (sel) sel.addEventListener('click', () => toggleOptions()); - } catch (e) { - console.warn('Custom graph dropdown not available', e); - } - - // custom dropdown is populated above via addGraphOption - - if (DOM.messageInput) DOM.messageInput.disabled = false; - if (DOM.submitButton) DOM.submitButton.disabled = false; - if (DOM.messageInput) DOM.messageInput.placeholder = 'Describe the SQL query you want...'; - - // Attach delete button handler if present - const deleteBtn = document.getElementById('delete-graph-btn'); - if (deleteBtn) { - deleteBtn.removeEventListener('click', onDeleteClick as EventListener); - deleteBtn.addEventListener('click', onDeleteClick as EventListener); - } - }) - .catch(error => { - 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); - } else { - addMessage('Sorry, there was an error fetching the available graphs: ' + (error as Error).message, false); - if (DOM.messageInput) DOM.messageInput.disabled = true; - if (DOM.submitButton) DOM.submitButton.disabled = true; - if (DOM.messageInput) DOM.messageInput.placeholder = 'Cannot connect to server'; + addMessage(`Graph "${name}" deleted.`); + } catch (err) { + console.error("Error deleting graph:", err); + addMessage("Error deleting graph: " + (err as Error).message); + } finally { + // Always refresh the graph list after delete attempt + loadGraphs(); + } } - - const option = document.createElement('option'); - option.value = ''; - option.textContent = (error as Error).message.includes('Authentication') ? 'Please log in' : 'Error loading graphs'; - option.disabled = true; + ); }); + + const sel = document.getElementById("graph-selected"); + if (sel) sel.addEventListener("click", () => toggleOptions()); + } catch (e) { + console.warn("Custom graph dropdown not available", e); + } + + // custom dropdown is populated above via addGraphOption + + if (DOM.messageInput) DOM.messageInput.disabled = false; + if (DOM.submitButton) DOM.submitButton.disabled = false; + if (DOM.messageInput) + DOM.messageInput.placeholder = "Describe the SQL query you want..."; + + // Attach delete button handler if present + const deleteBtn = document.getElementById("delete-graph-btn"); + if (deleteBtn) { + deleteBtn.removeEventListener("click", onDeleteClick as EventListener); + deleteBtn.addEventListener("click", onDeleteClick as EventListener); + } + }) + .catch((error) => { + console.error("Error fetching graphs:", error); + + if ((error as Error).message.includes("Authentication required")) { + 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 + ); + if (DOM.messageInput) DOM.messageInput.disabled = true; + if (DOM.submitButton) DOM.submitButton.disabled = true; + if (DOM.messageInput) + DOM.messageInput.placeholder = "Cannot connect to server"; + } + + const option = document.createElement("option"); + option.value = ""; + option.textContent = (error as Error).message.includes("Authentication") + ? "Please log in" + : "Error loading graphs"; + option.disabled = true; + }); } async function onDeleteClick() { - const graphName = getSelectedGraph(); - if (!graphName) { - addMessage('Please select a graph to delete.', false); - return; + const graphName = getSelectedGraph(); + if (!graphName) { + addMessage("Please select a graph to delete."); + return; + } + + const confirmed = confirm( + `Are you sure you want to delete the graph '${graphName}'? This action cannot be undone.` + ); + if (!confirmed) return; + + try { + const resp = await fetch(`/graphs/${encodeURIComponent(graphName)}`, { + method: "DELETE", + }); + if (!resp.ok) { + const text = await resp.text(); + throw new Error(`Failed to delete graph: ${resp.status} ${text}`); } - - const confirmed = confirm(`Are you sure you want to delete the graph '${graphName}'? This action cannot be undone.`); - if (!confirmed) return; - - try { - const resp = await fetch(`/graphs/${encodeURIComponent(graphName)}`, { method: 'DELETE' }); - if (!resp.ok) { - const text = await resp.text(); - throw new Error(`Failed to delete graph: ${resp.status} ${text}`); - } - addMessage(`Graph '${graphName}' deleted.`, false); - // Clear current chat state if the deleted graph was selected - if (window && (window as any).currentGraph === graphName) { - (window as any).currentGraph = undefined; - } - } catch (err) { - console.error('Error deleting graph:', err); - addMessage('Error deleting graph: ' + (err as Error).message, false); - } finally { - // Always reload graphs list after delete attempt - loadGraphs(); + addMessage(`Graph '${graphName}' deleted.`); + // Clear current chat state if the deleted graph was selected + if (window && (window as any).currentGraph === graphName) { + (window as any).currentGraph = undefined; } + } catch (err) { + console.error("Error deleting graph:", err); + addMessage("Error deleting graph: " + (err as Error).message); + } finally { + // Always reload graphs list after delete attempt + loadGraphs(); + } } export function handleFileUpload(event: Event) { - const target = event.target as HTMLInputElement | null; - const file = target?.files ? target.files[0] : null; - if (!file) return; - - const formData = new FormData(); - formData.append('file', file); - - fetch('/graphs', { - method: 'POST', - body: formData - }).then(response => response.json()) - .then(data => { - 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); - }); + const target = event.target as HTMLInputElement | null; + const file = target?.files ? target.files[0] : null; + if (!file) return; + + const formData = new FormData(); + formData.append("file", file); + + fetch("/graphs", { + method: "POST", + body: formData, + }) + .then((response) => response.json()) + .then((data) => { + 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 + ); + }); } export function onGraphChange() { - initChat(); + initChat(); } diff --git a/app/ts/modules/messages.ts b/app/ts/modules/messages.ts index 87666bb9..cf586773 100644 --- a/app/ts/modules/messages.ts +++ b/app/ts/modules/messages.ts @@ -2,148 +2,191 @@ * Message handling and UI functions (TypeScript) */ -import { DOM, state } from './config'; +import { DOM, state } from "./config"; import { getSelectedGraph } from './graph_select'; 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) { - 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"); + table.id = "query-final-result-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.messageInput) DOM.messageInput.value = ""; + if (DOM.chatMessages) DOM.chatMessages.innerHTML = ""; + [DOM.confValue, DOM.expValue, DOM.missValue].forEach((element) => { + if (element) element.innerHTML = ""; + }); const selected = getSelectedGraph(); if (selected) { - addMessage('Hello! How can I help you today?', false); + 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.', false); + 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 = []; + state.questions_history = []; + state.result_history = []; } diff --git a/app/ts/modules/schema.ts b/app/ts/modules/schema.ts index 38063ada..1bbbf609 100644 --- a/app/ts/modules/schema.ts +++ b/app/ts/modules/schema.ts @@ -199,15 +199,9 @@ export function showGraph(data: any) { graphInstance.linkDirectionalArrowLength(6).linkDirectionalArrowRelPos(1); } - const zoomInButton = document.getElementById( - "schema-controls-zoom-in" - ) as HTMLButtonElement; - const zoomOutButton = document.getElementById( - "schema-controls-zoom-out" - ) as HTMLButtonElement; - const centerButton = document.getElementById( - "schema-controls-center" - ) as HTMLButtonElement; + const zoomInButton = document.getElementById("schema-controls-zoom-in") as HTMLButtonElement; + const zoomOutButton = document.getElementById("schema-controls-zoom-out") as HTMLButtonElement; + const centerButton = document.getElementById("schema-controls-center") as HTMLButtonElement; zoomInButton.addEventListener("click", () => { graphInstance.zoom(graphInstance.zoom() * 1.1); @@ -230,6 +224,7 @@ const center = () => { graphInstance.zoomToFit(500, padding); } }; + export function resizeGraph() { if (graphInstance) { const container = document.getElementById("schema-graph") as HTMLDivElement;