fullstuckdev commited on
Commit
645ad9d
·
verified ·
1 Parent(s): 4108cc2

Upload 23 files

Browse files
README.md CHANGED
@@ -1,11 +1,36 @@
1
- ---
2
- title: Sql Agent
3
- emoji: 💻
4
- colorFrom: green
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
next-env.d.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ /// <reference types="next/navigation-types/compat/navigation" />
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
next.config.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "text-to-sql",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev --turbopack",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@huggingface/inference": "^2.8.1",
13
+ "@langchain/community": "^0.0.31",
14
+ "@langchain/openai": "^0.0.14",
15
+ "@tabler/icons-react": "^3.22.0",
16
+ "chart.js": "^4.4.6",
17
+ "framer-motion": "^11.11.17",
18
+ "langchain": "^0.1.37",
19
+ "mysql2": "^3.11.4",
20
+ "next": "15.0.3",
21
+ "react": "^18.2.0",
22
+ "react-chartjs-2": "^5.2.0",
23
+ "react-dom": "^18.2.0",
24
+ "zod": "^3.22.4"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20",
28
+ "@types/react": "^18",
29
+ "@types/react-dom": "^18",
30
+ "eslint": "^8",
31
+ "eslint-config-next": "15.0.3",
32
+ "postcss": "^8",
33
+ "tailwindcss": "^3.4.1",
34
+ "typescript": "^5"
35
+ }
36
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
public/file.svg ADDED
public/globe.svg ADDED
public/next.svg ADDED
public/vercel.svg ADDED
public/window.svg ADDED
src/app/favicon.ico ADDED
src/app/fonts/GeistMonoVF.woff ADDED
Binary file (67.9 kB). View file
 
src/app/fonts/GeistVF.woff ADDED
Binary file (66.3 kB). View file
 
src/components/AIVisualization.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Bar, Pie, Line } from 'react-chartjs-2';
2
+ import {
3
+ Chart as ChartJS,
4
+ CategoryScale,
5
+ LinearScale,
6
+ BarElement,
7
+ LineElement,
8
+ PointElement,
9
+ ArcElement,
10
+ Title,
11
+ Tooltip,
12
+ Legend
13
+ } from 'chart.js';
14
+
15
+ ChartJS.register(
16
+ CategoryScale,
17
+ LinearScale,
18
+ BarElement,
19
+ LineElement,
20
+ PointElement,
21
+ ArcElement,
22
+ Title,
23
+ Tooltip,
24
+ Legend
25
+ );
26
+
27
+ interface AIVisualizationProps {
28
+ visualization: {
29
+ type: 'bar' | 'pie' | 'line' | 'doughnut';
30
+ config: any;
31
+ };
32
+ }
33
+
34
+ export default function AIVisualization({ visualization }: AIVisualizationProps) {
35
+ const ChartComponent = {
36
+ bar: Bar,
37
+ pie: Pie,
38
+ line: Line,
39
+ doughnut: Pie
40
+ }[visualization.type];
41
+
42
+ if (!ChartComponent) return null;
43
+
44
+ const options = {
45
+ responsive: true,
46
+ maintainAspectRatio: false,
47
+ plugins: {
48
+ legend: {
49
+ position: 'top' as const,
50
+ },
51
+ title: {
52
+ display: true,
53
+ text: 'Data Visualization'
54
+ }
55
+ },
56
+ ...(visualization.type === 'doughnut' && {
57
+ cutout: '50%'
58
+ })
59
+ };
60
+
61
+ return (
62
+ <div className="w-full h-[400px]">
63
+ <ChartComponent
64
+ data={visualization.config}
65
+ options={options}
66
+ />
67
+ </div>
68
+ );
69
+ }
src/pages/_app.tsx ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import type { AppProps } from 'next/app'
2
+ import '../styles/globals.css'
3
+
4
+ export default function App({ Component, pageProps }: AppProps) {
5
+ return <Component {...pageProps} />
6
+ }
src/pages/api/sql.ts ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { HfInference } from "@huggingface/inference";
2
+ import { NextApiRequest, NextApiResponse } from "next";
3
+ import { createConnection, executeQuery } from "@/utils/database";
4
+
5
+ export default async function handler(
6
+ req: NextApiRequest,
7
+ res: NextApiResponse
8
+ ) {
9
+ if (req.method !== "POST") {
10
+ return res.status(405).json({ message: "Method not allowed" });
11
+ }
12
+
13
+ const { dbUri, userPrompt } = req.body;
14
+
15
+ if (!dbUri || !userPrompt) {
16
+ return res.status(400).json({
17
+ message: "Missing required fields",
18
+ details: {
19
+ dbUri: !dbUri ? "Database URI is required" : null,
20
+ userPrompt: !userPrompt ? "Query prompt is required" : null
21
+ }
22
+ });
23
+ }
24
+
25
+ try {
26
+ const apiKey = process.env.HUGGINGFACE_API_KEY;
27
+ if (!apiKey) {
28
+ return res.status(500).json({
29
+ message: "Server configuration error",
30
+ details: "API key is not configured"
31
+ });
32
+ }
33
+
34
+ let hf;
35
+ try {
36
+ hf = new HfInference(apiKey);
37
+ } catch (error: any) {
38
+ return res.status(500).json({
39
+ message: "Failed to initialize AI model",
40
+ details: error.message
41
+ });
42
+ }
43
+
44
+ let response;
45
+ const prompt = `You are a SQL expert. Convert the following text to a SQL query.
46
+ Rules:
47
+ - Return a JSON object with exactly this format: {"query": "YOUR SQL QUERY HERE", "chartType": "CHART TYPE HERE"}
48
+ - For chartType use one of: "bar", "pie", "line", "doughnut", or null
49
+ - The query should be safe and only return the requested data
50
+ - Keep table names exactly as provided
51
+ - Do not include any explanations or comments
52
+
53
+ Example input: "Show me sales data as a pie chart"
54
+ Example output: {"query": "SELECT * FROM sales LIMIT 10", "chartType": "pie"}
55
+
56
+ Text: ${userPrompt}`;
57
+
58
+ try {
59
+ response = await hf.chatCompletion({
60
+ model: "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
61
+ messages: [{ role: "user", content: prompt }],
62
+ temperature: 0.1,
63
+ max_tokens: 500,
64
+ });
65
+ console.log(response);
66
+ } catch (error: any) {
67
+ console.log(error);
68
+ return res.status(500).json({
69
+ message: "AI model error",
70
+ details: error.message || "Failed to generate SQL query"
71
+ });
72
+ }
73
+
74
+ let sqlQuery = '';
75
+ let requestedChartType = null;
76
+ try {
77
+ const content = response?.choices?.[0]?.message?.content?.trim() || '';
78
+
79
+ const jsonMatch = content.match(/\{.*\}/s);
80
+ if (jsonMatch) {
81
+ const parsedResponse = JSON.parse(jsonMatch[0]);
82
+ sqlQuery = parsedResponse.query?.trim();
83
+ requestedChartType = parsedResponse.chartType;
84
+ } else {
85
+ sqlQuery = content
86
+ .replace(/```sql/gi, '')
87
+ .replace(/```/gi, '')
88
+ .replace(/sql query:?\s*/gi, '')
89
+ .replace(/query:?\s*/gi, '')
90
+ .trim();
91
+ }
92
+
93
+ if (!sqlQuery) {
94
+ throw new Error('No valid SQL query found in response');
95
+ }
96
+ } catch (error: any) {
97
+ return res.status(500).json({
98
+ message: "Failed to parse AI response",
99
+ details: error.message
100
+ });
101
+ }
102
+
103
+ let connection;
104
+ try {
105
+ connection = await createConnection(dbUri);
106
+ } catch (error: any) {
107
+ return res.status(500).json({
108
+ message: "Database connection error",
109
+ details: error.message
110
+ });
111
+ }
112
+
113
+ try {
114
+ const results = await executeQuery(connection, sqlQuery || '');
115
+
116
+ let visualization = null;
117
+ if (Array.isArray(results) && results.length > 0) {
118
+ const firstRow = results[0];
119
+ const columns = Object.keys(firstRow);
120
+
121
+ const dataAnalysis = {
122
+ totalColumns: columns.length,
123
+ numericColumns: columns.filter(col =>
124
+ typeof firstRow[col] === 'number' &&
125
+ !col.toLowerCase().includes('id') &&
126
+ !col.toLowerCase().includes('_id')
127
+ ),
128
+ dateColumns: columns.filter(col => firstRow[col] instanceof Date),
129
+ stringColumns: columns.filter(col =>
130
+ typeof firstRow[col] === 'string' ||
131
+ col.toLowerCase().includes('name') ||
132
+ col.toLowerCase().includes('title')
133
+ ),
134
+ rowCount: results.length
135
+ };
136
+
137
+ if (requestedChartType === 'pie' || requestedChartType === 'doughnut') {
138
+ const preferredNumericColumns = dataAnalysis.numericColumns.filter(col =>
139
+ col.toLowerCase().includes('status') ||
140
+ col.toLowerCase().includes('count') ||
141
+ col.toLowerCase().includes('amount') ||
142
+ col.toLowerCase().includes('total')
143
+ );
144
+
145
+ if (preferredNumericColumns.length > 0) {
146
+ dataAnalysis.numericColumns = preferredNumericColumns;
147
+ }
148
+ }
149
+
150
+ if (requestedChartType) {
151
+ switch (requestedChartType) {
152
+ case 'pie':
153
+ case 'doughnut':
154
+ if (dataAnalysis.numericColumns.length > 0) {
155
+ visualization = {
156
+ type: requestedChartType,
157
+ config: {
158
+ labels: results.map(row =>
159
+ dataAnalysis.stringColumns[0]
160
+ ? String(row[dataAnalysis.stringColumns[0]])
161
+ : `Row ${results.indexOf(row) + 1}`
162
+ ),
163
+ datasets: [{
164
+ data: results.map(row => row[dataAnalysis.numericColumns[0]]),
165
+ backgroundColor: results.map(() =>
166
+ `hsla(${Math.random() * 360}, 70%, 50%, 0.6)`
167
+ )
168
+ }]
169
+ }
170
+ };
171
+ }
172
+ break;
173
+
174
+ case 'line':
175
+ if (dataAnalysis.dateColumns.length > 0 || dataAnalysis.numericColumns.length > 0) {
176
+ visualization = {
177
+ type: 'line',
178
+ config: {
179
+ labels: dataAnalysis.dateColumns.length > 0
180
+ ? results.map(row => new Date(row[dataAnalysis.dateColumns[0]]).toLocaleDateString())
181
+ : results.map((_, idx) => `Point ${idx + 1}`),
182
+ datasets: dataAnalysis.numericColumns.map(col => ({
183
+ label: col,
184
+ data: results.map(row => row[col]),
185
+ borderColor: `hsl(${Math.random() * 360}, 70%, 50%)`,
186
+ tension: 0.1
187
+ }))
188
+ }
189
+ };
190
+ }
191
+ break;
192
+
193
+ case 'bar':
194
+ default:
195
+ visualization = {
196
+ type: 'bar',
197
+ config: {
198
+ labels: dataAnalysis.stringColumns.length > 0
199
+ ? results.map(row => String(row[dataAnalysis.stringColumns[0]]))
200
+ : results.map((_, idx) => `Row ${idx + 1}`),
201
+ datasets: dataAnalysis.numericColumns.map(col => ({
202
+ label: col,
203
+ data: results.map(row => row[col]),
204
+ backgroundColor: `hsla(${Math.random() * 360}, 70%, 50%, 0.6)`,
205
+ borderColor: `hsl(${Math.random() * 360}, 70%, 50%)`,
206
+ borderWidth: 1
207
+ }))
208
+ }
209
+ };
210
+ break;
211
+ }
212
+ }
213
+ }
214
+
215
+ await connection.end();
216
+ return res.status(200).json({
217
+ results,
218
+ query: sqlQuery,
219
+ visualization
220
+ });
221
+ } catch (error: any) {
222
+ await connection?.end();
223
+ return res.status(500).json({
224
+ message: "Query execution error",
225
+ details: error.message
226
+ });
227
+ }
228
+
229
+ } catch (error: any) {
230
+ console.error('Unexpected API Error:', error);
231
+ return res.status(500).json({
232
+ message: "Unexpected error occurred",
233
+ details: error.message || "Unknown error"
234
+ });
235
+ }
236
+ }
src/pages/api/tables.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextApiRequest, NextApiResponse } from "next";
2
+ import { createConnection, getTables } from "@/utils/database";
3
+
4
+ export default async function handler(
5
+ req: NextApiRequest,
6
+ res: NextApiResponse
7
+ ) {
8
+ if (req.method !== "POST") {
9
+ return res.status(405).json({ message: "Method not allowed" });
10
+ }
11
+
12
+ const { dbUri } = req.body;
13
+
14
+ if (!dbUri) {
15
+ return res.status(400).json({
16
+ message: "Database URI is required"
17
+ });
18
+ }
19
+
20
+ let connection;
21
+ try {
22
+ connection = await createConnection(dbUri);
23
+ const tables = await getTables(connection);
24
+ await connection.end();
25
+ return res.status(200).json({ tables });
26
+ } catch (error: any) {
27
+ await connection?.end();
28
+ return res.status(500).json({
29
+ message: "Failed to fetch tables",
30
+ details: error.message
31
+ });
32
+ }
33
+ }
src/pages/index.tsx ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { IconDatabase, IconBrandMysql, IconArrowRight, IconLoader2, IconAlertCircle, IconRefresh } from '@tabler/icons-react';
4
+ import AIVisualization from '../components/AIVisualization';
5
+
6
+ interface QueryResults {
7
+ results: any;
8
+ query: string;
9
+ visualization?: {
10
+ type: 'bar' | 'pie' | 'line';
11
+ config: any;
12
+ };
13
+ }
14
+
15
+ interface ApiError {
16
+ message: string;
17
+ details?: string | Record<string, string | null>;
18
+ }
19
+
20
+ export default function Home() {
21
+ const [dbConfig, setDbConfig] = useState({
22
+ host: '',
23
+ port: '3306',
24
+ database: '',
25
+ username: '',
26
+ password: ''
27
+ });
28
+ const [tables, setTables] = useState<string[]>([]);
29
+ const [selectedTable, setSelectedTable] = useState('');
30
+ const [prompt, setPrompt] = useState('');
31
+ const [results, setResults] = useState<QueryResults | null>(null);
32
+ const [loading, setLoading] = useState(false);
33
+ const [error, setError] = useState<ApiError | null>(null);
34
+
35
+ const [dbUri, setDbUri] = useState('');
36
+ const [showDbConfig, setShowDbConfig] = useState(false);
37
+
38
+ useEffect(() => {
39
+ const uri = `mysql://${dbConfig.username}:${dbConfig.password}@${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`;
40
+ setDbUri(uri);
41
+ }, [dbConfig]);
42
+
43
+ const fetchTables = async () => {
44
+ if (!dbUri) return;
45
+
46
+ setLoading(true);
47
+ setError(null);
48
+ try {
49
+ const response = await fetch('/api/tables', {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify({ dbUri }),
53
+ });
54
+
55
+ const data = await response.json();
56
+
57
+ if (!response.ok) {
58
+ throw new Error(data.message);
59
+ }
60
+
61
+ setTables(data.tables);
62
+ } catch (err: any) {
63
+ setError({
64
+ message: 'Failed to fetch tables',
65
+ details: err.message
66
+ });
67
+ } finally {
68
+ setLoading(false);
69
+ }
70
+ };
71
+
72
+ const handleSubmit = async (e: React.FormEvent) => {
73
+ e.preventDefault();
74
+
75
+ if (!selectedTable) {
76
+ setError({
77
+ message: "Table selection required",
78
+ details: "Please select a table before submitting the query"
79
+ });
80
+ return;
81
+ }
82
+
83
+ setLoading(true);
84
+ setError(null);
85
+ setResults(null);
86
+
87
+ try {
88
+ const response = await fetch('/api/sql', {
89
+ method: 'POST',
90
+ headers: { 'Content-Type': 'application/json' },
91
+ body: JSON.stringify({
92
+ dbUri,
93
+ userPrompt: `${prompt} from table ${selectedTable}`
94
+ }),
95
+ });
96
+
97
+ const data = await response.json();
98
+
99
+ if (!response.ok) {
100
+ throw {
101
+ message: data.message || 'An error occurred',
102
+ details: data.details
103
+ };
104
+ }
105
+
106
+ setResults(data);
107
+ } catch (err: any) {
108
+ setError({
109
+ message: err.message || 'Failed to process request',
110
+ details: err.details
111
+ });
112
+ } finally {
113
+ setLoading(false);
114
+ }
115
+ };
116
+
117
+ return (
118
+ <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
119
+ <div className="container mx-auto px-4 py-12 max-w-4xl">
120
+ <motion.div
121
+ initial={{ opacity: 0, y: 20 }}
122
+ animate={{ opacity: 1, y: 0 }}
123
+ className="text-center mb-12"
124
+ >
125
+ <div className="flex items-center justify-center gap-3 mb-4">
126
+ <IconBrandMysql className="w-10 h-10 text-blue-500" />
127
+ <IconArrowRight className="w-6 h-6 text-gray-400" />
128
+ <IconDatabase className="w-10 h-10 text-blue-500" />
129
+ </div>
130
+ <h1 className="text-4xl font-bold text-gray-800 dark:text-white mb-4">
131
+ Text to SQL Converter
132
+ </h1>
133
+ <p className="text-gray-600 dark:text-gray-300">
134
+ Transform natural language into SQL queries using AI
135
+ </p>
136
+ </motion.div>
137
+
138
+ <motion.div
139
+ initial={{ opacity: 0, y: 20 }}
140
+ animate={{ opacity: 1, y: 0 }}
141
+ transition={{ delay: 0.2 }}
142
+ className="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-6 mb-8"
143
+ >
144
+ <form onSubmit={handleSubmit} className="space-y-6">
145
+ {error && (
146
+ <motion.div
147
+ initial={{ opacity: 0, height: 0 }}
148
+ animate={{ opacity: 1, height: 'auto' }}
149
+ exit={{ opacity: 0, height: 0 }}
150
+ className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4"
151
+ >
152
+ <div className="flex items-center gap-2 text-red-800 dark:text-red-200">
153
+ <IconAlertCircle className="w-5 h-5" />
154
+ <p className="font-medium">{error.message}</p>
155
+ </div>
156
+ {error.details && (
157
+ <p className="mt-2 text-sm text-red-600 dark:text-red-300">
158
+ {typeof error.details === 'string'
159
+ ? error.details
160
+ : Object.entries(error.details)
161
+ .filter(([_, value]) => value)
162
+ .map(([_, value]) => value)
163
+ .join(', ')}
164
+ </p>
165
+ )}
166
+ </motion.div>
167
+ )}
168
+
169
+ <div className="space-y-4">
170
+ <div className="flex justify-between items-center">
171
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-200">
172
+ Database Connection
173
+ </label>
174
+ <button
175
+ type="button"
176
+ onClick={() => setShowDbConfig(!showDbConfig)}
177
+ className="flex items-center gap-2 text-sm text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
178
+ >
179
+ {showDbConfig ? (
180
+ <>Hide Connection Details <IconArrowRight className="w-4 h-4 rotate-90" /></>
181
+ ) : (
182
+ <>Show Connection Details <IconArrowRight className="w-4 h-4 -rotate-90" /></>
183
+ )}
184
+ </button>
185
+ </div>
186
+
187
+ <AnimatePresence>
188
+ {showDbConfig && (
189
+ <motion.div
190
+ initial={{ opacity: 0, height: 0 }}
191
+ animate={{ opacity: 1, height: 'auto' }}
192
+ exit={{ opacity: 0, height: 0 }}
193
+ transition={{ duration: 0.2 }}
194
+ className="overflow-hidden"
195
+ >
196
+ <div className="grid grid-cols-2 gap-4">
197
+ <div>
198
+ <input
199
+ type="text"
200
+ value={dbConfig.host}
201
+ onChange={(e) => setDbConfig(prev => ({ ...prev, host: e.target.value }))}
202
+ className="w-full px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-all"
203
+ placeholder="Server Host"
204
+ />
205
+ </div>
206
+ <div>
207
+ <input
208
+ type="text"
209
+ value={dbConfig.port}
210
+ onChange={(e) => setDbConfig(prev => ({ ...prev, port: e.target.value }))}
211
+ className="w-full px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-all"
212
+ placeholder="Port"
213
+ />
214
+ </div>
215
+ <div>
216
+ <input
217
+ type="text"
218
+ value={dbConfig.database}
219
+ onChange={(e) => setDbConfig(prev => ({ ...prev, database: e.target.value }))}
220
+ className="w-full px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-all"
221
+ placeholder="Database Name"
222
+ />
223
+ </div>
224
+ <div>
225
+ <input
226
+ type="text"
227
+ value={dbConfig.username}
228
+ onChange={(e) => setDbConfig(prev => ({ ...prev, username: e.target.value }))}
229
+ className="w-full px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-all"
230
+ placeholder="Username"
231
+ />
232
+ </div>
233
+ <div className="col-span-2">
234
+ <input
235
+ type="password"
236
+ value={dbConfig.password}
237
+ onChange={(e) => setDbConfig(prev => ({ ...prev, password: e.target.value }))}
238
+ className="w-full px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-all"
239
+ placeholder="Password"
240
+ />
241
+ </div>
242
+ </div>
243
+ </motion.div>
244
+ )}
245
+ </AnimatePresence>
246
+ </div>
247
+
248
+ <div>
249
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
250
+ Select Table
251
+ </label>
252
+ <div className="flex gap-4">
253
+ <select
254
+ value={selectedTable}
255
+ onChange={(e) => setSelectedTable(e.target.value)}
256
+ className="flex-1 px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-all"
257
+ disabled={tables.length === 0}
258
+ >
259
+ <option value="">Select a table</option>
260
+ {tables.map((table) => (
261
+ <option key={table} value={table}>
262
+ {table}
263
+ </option>
264
+ ))}
265
+ </select>
266
+ <button
267
+ type="button"
268
+ onClick={fetchTables}
269
+ disabled={!dbUri || loading}
270
+ className="px-4 py-3 rounded-lg bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 transition-all disabled:opacity-50"
271
+ >
272
+ {loading ? (
273
+ <IconLoader2 className="w-5 h-5 animate-spin" />
274
+ ) : (
275
+ <IconRefresh className="w-5 h-5" />
276
+ )}
277
+ </button>
278
+ </div>
279
+ </div>
280
+
281
+ <div>
282
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
283
+ Your Query
284
+ </label>
285
+ <textarea
286
+ value={prompt}
287
+ onChange={(e) => setPrompt(e.target.value)}
288
+ rows={4}
289
+ className="w-full px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-all"
290
+ placeholder="Example: Show me the first 5 users from the users table"
291
+ />
292
+ </div>
293
+
294
+ <button
295
+ type="submit"
296
+ disabled={loading}
297
+ className="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-6 rounded-lg transition-all transform hover:scale-[1.02] active:scale-[0.98] disabled:opacity-70 disabled:cursor-not-allowed flex items-center justify-center gap-2"
298
+ >
299
+ {loading ? (
300
+ <>
301
+ <IconLoader2 className="w-5 h-5 animate-spin" />
302
+ Converting...
303
+ </>
304
+ ) : (
305
+ 'Convert to SQL'
306
+ )}
307
+ </button>
308
+ </form>
309
+ </motion.div>
310
+
311
+ {results && (
312
+ <motion.div
313
+ initial={{ opacity: 0, y: 20 }}
314
+ animate={{ opacity: 1, y: 0 }}
315
+ className="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-6"
316
+ >
317
+ <h2 className="text-xl font-semibold text-gray-800 dark:text-white mb-4">
318
+ Generated SQL & Results
319
+ </h2>
320
+ <div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-4">
321
+ <h3 className="text-sm font-medium text-gray-600 dark:text-gray-300 mb-2">
322
+ SQL Query
323
+ </h3>
324
+ <pre className="text-sm text-gray-800 dark:text-gray-200 whitespace-pre-wrap">
325
+ {results.query}
326
+ </pre>
327
+ </div>
328
+ <div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
329
+ <h3 className="text-sm font-medium text-gray-600 dark:text-gray-300 mb-2">
330
+ Query Results
331
+ </h3>
332
+ <pre className="text-sm text-gray-800 dark:text-gray-200 whitespace-pre-wrap">
333
+ {JSON.stringify(results.results, null, 2)}
334
+ </pre>
335
+ </div>
336
+ {results?.visualization && (
337
+ <div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mt-4">
338
+ <h3 className="text-sm font-medium text-gray-600 dark:text-gray-300 mb-4">
339
+ AI Generated Visualization
340
+ </h3>
341
+ <AIVisualization visualization={results.visualization} />
342
+ </div>
343
+ )}
344
+ </motion.div>
345
+ )}
346
+ </div>
347
+ </div>
348
+ );
349
+ }
src/styles/globals.css ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --background: #ffffff;
7
+ --foreground: #171717;
8
+ }
9
+
10
+ @media (prefers-color-scheme: dark) {
11
+ :root {
12
+ --background: #0a0a0a;
13
+ --foreground: #ededed;
14
+ }
15
+ }
16
+
17
+ body {
18
+ color: var(--foreground);
19
+ background: var(--background);
20
+ font-family: Arial, Helvetica, sans-serif;
21
+ }
src/utils/database.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mysql from 'mysql2/promise';
2
+
3
+ export async function createConnection(uri: string) {
4
+ try {
5
+ const connection = await mysql.createConnection(uri);
6
+ return connection;
7
+ } catch (error) {
8
+ throw new Error('Failed to connect to database');
9
+ }
10
+ }
11
+
12
+ export async function executeQuery(connection: mysql.Connection, query: string) {
13
+ try {
14
+ const [rows] = await connection.execute(query);
15
+ return rows;
16
+ } catch (error) {
17
+ throw new Error(`Failed to execute query: ${error}`);
18
+ }
19
+ }
20
+
21
+ export async function getTables(connection: mysql.Connection) {
22
+ try {
23
+ const [rows] = await connection.execute('SHOW TABLES');
24
+ return Object.values(rows).map((row: any) => Object.values(row)[0]);
25
+ } catch (error) {
26
+ throw new Error('Failed to fetch tables');
27
+ }
28
+ }
29
+
30
+ export async function getTablesList(connection: mysql.Connection) {
31
+ try {
32
+ const [rows] = await connection.execute('SHOW TABLES');
33
+ return Object.values(rows).map((row: any) => Object.values(row)[0]);
34
+ } catch (error) {
35
+ throw new Error('Failed to fetch tables list');
36
+ }
37
+ }
tailwind.config.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from "tailwindcss";
2
+
3
+ export default {
4
+ content: [
5
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7
+ "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8
+ ],
9
+ theme: {
10
+ extend: {
11
+ colors: {
12
+ background: "var(--background)",
13
+ foreground: "var(--foreground)",
14
+ },
15
+ },
16
+ },
17
+ plugins: [],
18
+ } satisfies Config;
tsconfig.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }