diff --git a/src/api/constants.ts b/src/api/constants.ts index a05a26b9..75aa334b 100644 --- a/src/api/constants.ts +++ b/src/api/constants.ts @@ -1,6 +1,5 @@ const API_V1 = 'api/v1'; - // Streams Management export const LOG_STREAM_LIST_URL = `${API_V1}/logstream`; export const LOG_STREAMS_SCHEMA_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/schema`; @@ -17,4 +16,7 @@ export const ABOUT_URL = `${API_V1}/about`; export const USERS_LIST_URL = `${API_V1}/user`; export const USER_URL = (username: string) => `${USERS_LIST_URL}/${username}`; export const USER_ROLES_URL = (username: string) => `${USER_URL(username)}/role`; -export const USER_PASSWORD_URL = (username: string) => `${USER_URL(username)}/generate-new-password`; \ No newline at end of file +export const USER_PASSWORD_URL = (username: string) => `${USER_URL(username)}/generate-new-password`; + +// LLM queries +export const LLM_QUERY_URL = `${API_V1}/llm`; diff --git a/src/pages/Query/QueryCodeEditor.tsx b/src/pages/Query/QueryCodeEditor.tsx index f7ba36d2..832aa3b4 100644 --- a/src/pages/Query/QueryCodeEditor.tsx +++ b/src/pages/Query/QueryCodeEditor.tsx @@ -2,7 +2,7 @@ import React, { FC, useEffect } from 'react'; import Editor from '@monaco-editor/react'; import { useQueryPageContext } from './Context'; import { useHeaderContext } from '@/layouts/MainLayout/Context'; -import { Box, Button, Text, Tooltip, px } from '@mantine/core'; +import { Box, Button, Input, Text, Tooltip, px } from '@mantine/core'; import { useQueryResult } from '@/hooks/useQueryResult'; import { ErrorMarker, errChecker } from './ErrorMarker'; import { notifications } from '@mantine/notifications'; @@ -10,6 +10,9 @@ import { IconPlayerPlayFilled, IconCheck, IconFileAlert, IconFileInfo } from '@t import useMountedState from '@/hooks/useMountedState'; import { useQueryCodeEditorStyles } from './styles'; import dayjs from 'dayjs'; +import { notify } from '@/utils/notification'; +import { Axios } from '@/api/axios'; +import { LLM_QUERY_URL } from '@/api/constants'; const QueryCodeEditor: FC = () => { const { @@ -25,7 +28,30 @@ const QueryCodeEditor: FC = () => { const [isSchemaOpen, setIsSchemaOpen] = useMountedState(false); const [refreshInterval, setRefreshInterval] = useMountedState(null); const [currentStreamName, setCurrentStreamName] = useMountedState(subLogQuery.get().streamName); - const [query, setQuery] = useMountedState(""); + const [query, setQuery] = useMountedState(''); + const [aiQuery, setAiQuery] = useMountedState('Show all records'); + + const handleAIGenerate = async () => { + if (!aiQuery?.length) { + notify({ message: 'Please enter a valid query' }); + return; + } + notify({ message: 'AI based SQL being generated.', title: 'Getting suggestions', autoClose: 3000, color: 'blue' }); + + const resp = await Axios().post(LLM_QUERY_URL, { prompt: aiQuery, stream: currentStreamName }); + if (resp.status !== 200) { + notify({ + message: 'Please check your internet connection and add a valid OpenAI API key', + title: 'Error getting suggestions', + color: 'red', + }); + return; + } + + const warningMsg = + '-- Parseable AI is experimental and may produce incorrect answers\n-- Always verify the generated SQL before executing\n\n'; + setQuery(warningMsg + resp.data); + }; const handleEditorChange = (code: any) => { setQuery(code); @@ -61,12 +87,11 @@ const QueryCodeEditor: FC = () => { }; }, [subLogQuery.get(), subSchemaToggle.get(), subRefreshInterval.get()]); -useEffect(() => { - if(subLogQuery.get().streamName){ - setQuery(`SELECT * FROM ${subLogQuery.get().streamName} LIMIT 100 ; `); - } -}, []); - + useEffect(() => { + if (subLogQuery.get().streamName) { + setQuery(`SELECT * FROM ${subLogQuery.get().streamName} LIMIT 100 ; `); + } + }, []); function handleEditorDidMount(editor: any, monaco: any) { editorRef.current = editor; @@ -75,7 +100,17 @@ useEffect(() => { runQuery(editor.getValue()); }); } - const runQuery = (query:string) => { + + const sanitseSqlString = (sqlString: string): string => { + const withoutComments = sqlString.replace(/--.*$/gm, ''); + const withoutNewLines = withoutComments.replace(/\n/g, ' '); + const withoutTrailingSemicolon = withoutNewLines.replace(/;/, ''); + return withoutTrailingSemicolon; + }; + + const runQuery = (inputQuery: string) => { + const query = sanitseSqlString(inputQuery); + resetData(); notifications.show({ id: 'load-data', @@ -86,25 +121,26 @@ useEffect(() => { autoClose: false, withCloseButton: false, }); - let LogQuery ={ - startTime : subLogQuery.get().startTime, - endTime :subLogQuery.get().endTime, - streamName : currentStreamName, - access:[] - } - if (subLogSelectedTimeRange.get().state==='fixed') { + let LogQuery = { + startTime: subLogQuery.get().startTime, + endTime: subLogQuery.get().endTime, + streamName: currentStreamName, + access: [], + }; + if (subLogSelectedTimeRange.get().state === 'fixed') { const now = dayjs(); const timeDiff = subLogQuery.get().endTime.getTime() - subLogQuery.get().startTime.getTime(); - LogQuery ={ - startTime : now.subtract(timeDiff).toDate(), - endTime :now.toDate(), - streamName : currentStreamName, - access:[] - } + LogQuery = { + startTime: now.subtract(timeDiff).toDate(), + endTime: now.toDate(), + streamName: currentStreamName, + access: [], + }; } const parsedQuery = query.replace(/(\r\n|\n|\r)/gm, ''); getQueryData(LogQuery, parsedQuery); }; + useEffect(() => { if (error) { notifications.update({ @@ -158,13 +194,32 @@ useEffect(() => { sx={{ color: 'white', backgroundColor: 'black' }} withArrow position="right"> - + + setAiQuery(e.target.value)} + placeholder="Ask Parseable AI" + /> + +