Skip to content
This repository was archived by the owner on May 13, 2025. It is now read-only.

Commit 7937ade

Browse files
authored
Add AI based SQL generator from natural language (#133)
Show AI query box only if LLM is enabled
1 parent 625478e commit 7937ade

File tree

4 files changed

+120
-98
lines changed

4 files changed

+120
-98
lines changed

src/@types/parseable/api/about.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
export type AboutData ={
2-
commit : string;
3-
deploymentId : string;
4-
latestVersion : string;
5-
license : string;
6-
mode : string;
7-
staging : string;
8-
store : string;
9-
updateAvailable : boolean;
10-
version : string;
11-
}
1+
export type AboutData = {
2+
commit: string;
3+
deploymentId: string;
4+
latestVersion: string;
5+
license: string;
6+
mode: string;
7+
staging: string;
8+
store: string;
9+
updateAvailable: boolean;
10+
version: string;
11+
llmActive: boolean;
12+
llmProvider: string;
13+
};

src/api/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export const USER_PASSWORD_URL = (username: string) => `${USER_URL(username)}/ge
2020

2121
// LLM queries
2222
export const LLM_QUERY_URL = `${API_V1}/llm`;
23+
export const IS_LLM_ACTIVE_URL = `${LLM_QUERY_URL}/isactive`;

src/components/Navbar/infoModal.tsx

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Box, Button, Modal, Text, Tooltip, px } from '@mantine/core';
2-
import { FC, useEffect } from 'react';
2+
import { FC, useEffect, useMemo } from 'react';
33
import { useInfoModalStyles } from './styles';
44
import { useGetAbout } from '@/hooks/useGetAbout';
55
import { IconAlertCircle, IconBook2, IconBrandGithub, IconBrandSlack, IconBusinessplan } from '@tabler/icons-react';
@@ -69,6 +69,14 @@ const InfoModal: FC<InfoModalProps> = (props) => {
6969
};
7070
}, []);
7171

72+
const llmStatus = useMemo(() => {
73+
let status = 'LLM API Key not set';
74+
if (data?.llmActive) {
75+
status = `${data.llmProvider} configured`;
76+
}
77+
return status;
78+
}, [data?.llmActive]);
79+
7280
const { classes } = useInfoModalStyles();
7381
const {
7482
container,
@@ -80,96 +88,91 @@ const InfoModal: FC<InfoModalProps> = (props) => {
8088
aboutTextKey,
8189
aboutTextValue,
8290
aboutTextInnerBox,
83-
actionBtnRed
91+
actionBtnRed,
8492
} = classes;
8593

8694
return (
87-
<Modal
88-
opened={opened}
89-
onClose={close}
90-
withinPortal
91-
withCloseButton={false}
92-
size="xl"
93-
centered>
95+
<Modal opened={opened} onClose={close} withinPortal withCloseButton={false} size="xl" centered>
9496
<Box className={container}>
95-
96-
<Text className={aboutTitle}>About Parseable</Text>
97-
<Text className={aboutDescription} id="info-modal-description">Important info about your Parseable deployment</Text>
98-
{error ? (
99-
<Text className={aboutDescription}>Error...</Text>
100-
) : loading ? (
101-
<Text className={aboutDescription}>Loading...</Text>
102-
) : data ? (
103-
<>
104-
<Box className={aboutTextBox}>
105-
<Box className={aboutTextInnerBox}>
106-
<Text className={aboutTextKey}> License: </Text>
107-
<Text className={aboutTextValue}> {data.license} </Text>
108-
<Button
97+
<Text className={aboutTitle}>About Parseable</Text>
98+
<Text className={aboutDescription} id="info-modal-description">
99+
Important info about your Parseable deployment
100+
</Text>
101+
{error ? (
102+
<Text className={aboutDescription}>Error...</Text>
103+
) : loading ? (
104+
<Text className={aboutDescription}>Loading...</Text>
105+
) : data ? (
106+
<>
107+
<Box className={aboutTextBox}>
108+
<Box className={aboutTextInnerBox}>
109+
<Text className={aboutTextKey}> License: </Text>
110+
<Text className={aboutTextValue}> {data.license} </Text>
111+
<Button
109112
variant="outline"
110113
component={'a'}
111114
href="mailto:[email protected]?subject=Production%20Support%20Query"
112115
target="_blank"
113-
className={actionBtn}
114-
>
116+
className={actionBtn}>
115117
Upgrade to production support
116118
</Button>
117-
</Box>
118119
</Box>
119-
<Box className={aboutTextBox}>
120-
<Box className={aboutTextInnerBox}>
121-
<Text className={aboutTextKey}> Commit: </Text>
122-
<Text className={aboutTextValue}> {data.commit} </Text>
123-
</Box>
124-
<Box className={aboutTextInnerBox}>
125-
<Text className={aboutTextKey}> Version: </Text>
126-
<Text className={aboutTextValue}> {data.version} </Text>
127-
{data.updateAvailable ? (
128-
<Button
120+
</Box>
121+
<Box className={aboutTextBox}>
122+
<Box className={aboutTextInnerBox}>
123+
<Text className={aboutTextKey}> Commit: </Text>
124+
<Text className={aboutTextValue}> {data.commit} </Text>
125+
</Box>
126+
<Box className={aboutTextInnerBox}>
127+
<Text className={aboutTextKey}> Version: </Text>
128+
<Text className={aboutTextValue}> {data.version} </Text>
129+
{data.updateAvailable ? (
130+
<Button
129131
variant="outline"
130132
component={'a'}
131133
href="https://github.com/parseablehq/parseable/releases/latest"
132134
target="_blank"
133135
className={actionBtnRed}
134-
leftIcon={<IconAlertCircle size={px('1.2rem')} stroke={1.5} />}
135-
>
136+
leftIcon={<IconAlertCircle size={px('1.2rem')} stroke={1.5} />}>
136137
Upgrade to latest version {data.latestVersion}
137-
</Button> ): null}
138-
</Box>
139-
138+
</Button>
139+
) : null}
140140
</Box>
141-
<Box className={aboutTextBox}>
142-
<Box className={aboutTextInnerBox}>
143-
<Text className={aboutTextKey}> Deployment Id: </Text>
144-
<Text className={aboutTextValue}> {data.deploymentId} </Text>
145-
</Box>
146-
<Box className={aboutTextInnerBox}>
147-
<Text className={aboutTextKey}>Mode</Text>
148-
<Text className={aboutTextValue}>{data.mode}</Text>
149-
</Box>
150-
<Box className={aboutTextInnerBox}>
151-
<Text className={aboutTextKey}>Staging</Text>
152-
<Text className={aboutTextValue}>{data.staging}</Text>
153-
</Box>
154-
<Box className={aboutTextInnerBox}>
155-
<Text className={aboutTextKey}>Store</Text>
156-
<Text className={aboutTextValue}>{data.store}</Text>
157-
</Box>
141+
</Box>
142+
<Box className={aboutTextBox}>
143+
<Box className={aboutTextInnerBox}>
144+
<Text className={aboutTextKey}> Deployment Id: </Text>
145+
<Text className={aboutTextValue}> {data.deploymentId} </Text>
158146
</Box>
159-
</>
160-
) : null}
161-
162-
<Text className={aboutTitle}>Need help?</Text>
163-
<Text className={aboutDescription}>Ensure uninterrupted deployment</Text>
147+
<Box className={aboutTextInnerBox}>
148+
<Text className={aboutTextKey}>Mode</Text>
149+
<Text className={aboutTextValue}>{data.mode}</Text>
150+
</Box>
151+
<Box className={aboutTextInnerBox}>
152+
<Text className={aboutTextKey}>Staging</Text>
153+
<Text className={aboutTextValue}>{data.staging}</Text>
154+
</Box>
155+
<Box className={aboutTextInnerBox}>
156+
<Text className={aboutTextKey}>Store</Text>
157+
<Text className={aboutTextValue}>{data.store}</Text>
158+
</Box>
159+
<Box className={aboutTextInnerBox}>
160+
<Text className={aboutTextKey}>LLM Status</Text>
161+
<Text className={aboutTextValue}>{llmStatus}</Text>
162+
</Box>
163+
</Box>
164+
</>
165+
) : null}
164166

165-
<Box mt={15} className={helpIconContainer}>
166-
{helpResources.map((data) => (
167-
<HelpCard key={data.title} data={data} />
168-
))}
169-
</Box>
167+
<Text className={aboutTitle}>Need help?</Text>
168+
<Text className={aboutDescription}>Ensure uninterrupted deployment</Text>
170169

170+
<Box mt={15} className={helpIconContainer}>
171+
{helpResources.map((data) => (
172+
<HelpCard key={data.title} data={data} />
173+
))}
171174
</Box>
172-
175+
</Box>
173176
</Modal>
174177
);
175178
};

src/pages/Query/QueryCodeEditor.tsx

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, useEffect } from 'react';
1+
import React, { FC, useCallback, useEffect, useMemo } from 'react';
22
import Editor from '@monaco-editor/react';
33
import { useQueryPageContext } from './Context';
44
import { useHeaderContext } from '@/layouts/MainLayout/Context';
@@ -13,6 +13,7 @@ import dayjs from 'dayjs';
1313
import { notify } from '@/utils/notification';
1414
import { Axios } from '@/api/axios';
1515
import { LLM_QUERY_URL } from '@/api/constants';
16+
import { useGetAbout } from '@/hooks/useGetAbout';
1617

1718
const QueryCodeEditor: FC = () => {
1819
const {
@@ -30,13 +31,20 @@ const QueryCodeEditor: FC = () => {
3031
const [currentStreamName, setCurrentStreamName] = useMountedState<string>(subLogQuery.get().streamName);
3132
const [query, setQuery] = useMountedState<string>('');
3233
const [aiQuery, setAiQuery] = useMountedState('Show all records');
34+
const { data: aboutData, getAbout } = useGetAbout();
35+
const isLlmActive = useMemo(() => aboutData?.llmActive, [aboutData?.llmActive]);
3336

34-
const handleAIGenerate = async () => {
37+
const handleAIGenerate = useCallback(async () => {
3538
if (!aiQuery?.length) {
3639
notify({ message: 'Please enter a valid query' });
3740
return;
3841
}
39-
notify({ message: 'AI based SQL being generated.', title: 'Getting suggestions', autoClose: 3000, color: 'blue' });
42+
notify({
43+
message: 'AI based SQL being generated.',
44+
title: 'Getting suggestions',
45+
autoClose: 3000,
46+
color: 'blue',
47+
});
4048

4149
const resp = await Axios().post(LLM_QUERY_URL, { prompt: aiQuery, stream: currentStreamName });
4250
if (resp.status !== 200) {
@@ -51,7 +59,7 @@ const QueryCodeEditor: FC = () => {
5159
const warningMsg =
5260
'-- Parseable AI is experimental and may produce incorrect answers\n-- Always verify the generated SQL before executing\n\n';
5361
setQuery(warningMsg + resp.data);
54-
};
62+
}, [aiQuery]);
5563

5664
const handleEditorChange = (code: any) => {
5765
setQuery(code);
@@ -91,6 +99,7 @@ const QueryCodeEditor: FC = () => {
9199
if (subLogQuery.get().streamName) {
92100
setQuery(`SELECT * FROM ${subLogQuery.get().streamName} LIMIT 100 ; `);
93101
}
102+
getAbout();
94103
}, []);
95104

96105
function handleEditorDidMount(editor: any, monaco: any) {
@@ -176,6 +185,11 @@ const QueryCodeEditor: FC = () => {
176185
<Box className={container}>
177186
<Text className={textContext}>Query</Text>
178187
<Box style={{ height: '100%', width: '100%', textAlign: 'right' }}>
188+
{!isLlmActive ? (
189+
<a style={{ marginRight: '2rem' }} href="https://www.parseable.io/docs/api/llm-queries">
190+
Enable SQL generation with OpenAI
191+
</a>
192+
) : null}
179193
<Tooltip
180194
label={`View Schema for ${subLogQuery.get().streamName}`}
181195
sx={{ color: 'white', backgroundColor: 'black' }}
@@ -206,20 +220,22 @@ const QueryCodeEditor: FC = () => {
206220
</Box>
207221
</Box>
208222
<Box sx={{ marginTop: '5px', height: 'calc(100% - 60px)' }}>
209-
<Box className="flex" style={{ display: 'flex', margin: '15px', flexWrap: 'wrap' }}>
210-
<Input
211-
type="text"
212-
name="ai_query"
213-
id="ai_query"
214-
style={{ minWidth: '85%', margin: '2px 20px 10px 0' }}
215-
value={aiQuery}
216-
onChange={(e) => setAiQuery(e.target.value)}
217-
placeholder="Ask Parseable AI"
218-
/>
219-
<Button variant="gradient" onClick={handleAIGenerate}>
220-
Generate SQL
221-
</Button>
222-
</Box>
223+
{isLlmActive ? (
224+
<Box className="flex" style={{ display: 'flex', margin: '15px', flexWrap: 'wrap' }}>
225+
<Input
226+
type="text"
227+
name="ai_query"
228+
id="ai_query"
229+
style={{ minWidth: '85%', margin: '2px 20px 10px 0' }}
230+
value={aiQuery}
231+
onChange={(e) => setAiQuery(e.target.value)}
232+
placeholder="Ask Parseable AI"
233+
/>
234+
<Button variant="gradient" onClick={handleAIGenerate}>
235+
Generate SQL
236+
</Button>
237+
</Box>
238+
) : null}
223239
<Editor
224240
height={'100%'}
225241
defaultLanguage="sql"

0 commit comments

Comments
 (0)