初始化项目;更新至 v0.1.0_20251209 版本
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
228
App.tsx
Normal file
228
App.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { generateSql } from './services/gemini';
|
||||||
|
import { Button } from './components/Button';
|
||||||
|
import { TextArea } from './components/TextArea';
|
||||||
|
import { Select } from './components/Select';
|
||||||
|
import { LoadingState, DatabaseType } from './types';
|
||||||
|
|
||||||
|
// Default placeholders to help the user understand what to input
|
||||||
|
const PLACEHOLDER_STRUCTURE = `例如:
|
||||||
|
CREATE TABLE student (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
name VARCHAR(50) COMMENT '姓名',
|
||||||
|
school_code VARCHAR(20) COMMENT '学校代码',
|
||||||
|
nation_code VARCHAR(10) COMMENT '国籍代码',
|
||||||
|
politics_code VARCHAR(10) COMMENT '政治面貌代码',
|
||||||
|
year VARCHAR(4) COMMENT '年度',
|
||||||
|
address VARCHAR(200) COMMENT '家庭住址'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE school (
|
||||||
|
code VARCHAR(20),
|
||||||
|
name VARCHAR(100)
|
||||||
|
);`;
|
||||||
|
|
||||||
|
const PLACEHOLDER_DICT = `例如:
|
||||||
|
字典表 dict_common (
|
||||||
|
type_code VARCHAR(50), -- 字典类型,如 'NATION', 'POLITICS'
|
||||||
|
item_code VARCHAR(50), -- 实际值,如 '01', 'CN'
|
||||||
|
item_name VARCHAR(100) -- 显示名,如 '汉族', '中国'
|
||||||
|
)
|
||||||
|
|
||||||
|
关联关系:
|
||||||
|
- student.nation_code -> dict_common (type_code='NATION')
|
||||||
|
- student.politics_code -> dict_common (type_code='POLITICS')`;
|
||||||
|
|
||||||
|
const PLACEHOLDER_REQ = `例如:
|
||||||
|
我需要查询学校基本信息。
|
||||||
|
输出:学校名称,学校代码,学生姓名,手机号,家庭住址,年度,政治面貌(需要字典翻译),国籍(需要字典翻译)。`;
|
||||||
|
|
||||||
|
const DB_OPTIONS = [
|
||||||
|
{ value: 'MySQL', label: 'MySQL / MariaDB' },
|
||||||
|
{ value: 'PostgreSQL', label: 'PostgreSQL' },
|
||||||
|
{ value: 'Oracle', label: 'Oracle Database' },
|
||||||
|
{ value: 'SQL Server', label: 'SQL Server (MSSQL)' },
|
||||||
|
{ value: 'Hive', label: 'Hive / SparkSQL' },
|
||||||
|
{ value: 'Dm', label: '达梦数据库 (Dameng)' },
|
||||||
|
{ value: 'SQLite', label: 'SQLite' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [databaseType, setDatabaseType] = useState<DatabaseType>('MySQL');
|
||||||
|
const [tableStructure, setTableStructure] = useState<string>('');
|
||||||
|
const [dictionaryData, setDictionaryData] = useState<string>('');
|
||||||
|
const [requirement, setRequirement] = useState<string>('');
|
||||||
|
|
||||||
|
const [generatedSql, setGeneratedSql] = useState<string>('');
|
||||||
|
const [status, setStatus] = useState<LoadingState>(LoadingState.IDLE);
|
||||||
|
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
if (!tableStructure.trim() || !requirement.trim()) {
|
||||||
|
setErrorMsg("请至少填写表结构和查询需求。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus(LoadingState.LOADING);
|
||||||
|
setErrorMsg(null);
|
||||||
|
setGeneratedSql('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sql = await generateSql({
|
||||||
|
tableStructure,
|
||||||
|
dictionaryData,
|
||||||
|
requirement,
|
||||||
|
databaseType
|
||||||
|
});
|
||||||
|
setGeneratedSql(sql);
|
||||||
|
setStatus(LoadingState.SUCCESS);
|
||||||
|
} catch (err: any) {
|
||||||
|
setErrorMsg(err.message || "未知错误");
|
||||||
|
setStatus(LoadingState.ERROR);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
if (generatedSql) {
|
||||||
|
navigator.clipboard.writeText(generatedSql);
|
||||||
|
// Optional: Show a toast here, but for now we'll just rely on user action
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-screen bg-slate-50">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between shrink-0">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-bold text-slate-800">SQL Translate Pro</h1>
|
||||||
|
<p className="text-xs text-slate-500">智能 SQL 生成 & 字典自动转义工具</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{/* Placeholder for future user settings or profile */}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content - Two Columns Layout */}
|
||||||
|
<main className="flex-1 overflow-hidden flex flex-col md:flex-row">
|
||||||
|
|
||||||
|
{/* Left Panel: Inputs */}
|
||||||
|
<div className="w-full md:w-1/2 lg:w-5/12 p-6 flex flex-col gap-6 overflow-y-auto border-r border-gray-200 bg-white">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
label="目标数据库类型"
|
||||||
|
options={DB_OPTIONS}
|
||||||
|
value={databaseType}
|
||||||
|
onChange={(e) => setDatabaseType(e.target.value as DatabaseType)}
|
||||||
|
helperText="生成结果将适配所选数据库的方言"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-h-[200px]">
|
||||||
|
<TextArea
|
||||||
|
label="1. 表结构与字段说明"
|
||||||
|
helperText="粘贴 DDL 或字段描述"
|
||||||
|
placeholder={PLACEHOLDER_STRUCTURE}
|
||||||
|
value={tableStructure}
|
||||||
|
onChange={(e) => setTableStructure(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-h-[150px]">
|
||||||
|
<TextArea
|
||||||
|
label="2. 字典表信息"
|
||||||
|
helperText="说明字典表结构及关联方式"
|
||||||
|
placeholder={PLACEHOLDER_DICT}
|
||||||
|
value={dictionaryData}
|
||||||
|
onChange={(e) => setDictionaryData(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-h-[120px]">
|
||||||
|
<TextArea
|
||||||
|
label="3. 最终需求"
|
||||||
|
helperText="你想要查询哪些字段?"
|
||||||
|
placeholder={PLACEHOLDER_REQ}
|
||||||
|
value={requirement}
|
||||||
|
onChange={(e) => setRequirement(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2 sticky bottom-0 bg-white pb-2">
|
||||||
|
<Button
|
||||||
|
onClick={handleGenerate}
|
||||||
|
isLoading={status === LoadingState.LOADING}
|
||||||
|
className="w-full shadow-lg"
|
||||||
|
>
|
||||||
|
生成 SQL 查询
|
||||||
|
</Button>
|
||||||
|
{errorMsg && (
|
||||||
|
<div className="mt-2 text-red-500 text-sm bg-red-50 p-2 rounded border border-red-100">
|
||||||
|
{errorMsg}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Panel: Output */}
|
||||||
|
<div className="w-full md:w-1/2 lg:w-7/12 bg-slate-50 flex flex-col overflow-hidden relative">
|
||||||
|
|
||||||
|
<div className="px-6 py-4 border-b border-gray-200 bg-white flex justify-between items-center shrink-0">
|
||||||
|
<h2 className="text-lg font-semibold text-slate-700">生成结果 <span className="text-xs font-normal text-slate-400 ml-2">({databaseType})</span></h2>
|
||||||
|
{status === LoadingState.SUCCESS && (
|
||||||
|
<Button variant="outline" onClick={copyToClipboard} className="text-xs py-1.5 h-8">
|
||||||
|
复制 SQL
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 p-6 overflow-y-auto">
|
||||||
|
{status === LoadingState.IDLE && (
|
||||||
|
<div className="h-full flex flex-col items-center justify-center text-slate-400">
|
||||||
|
<svg className="w-16 h-16 mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||||
|
</svg>
|
||||||
|
<p>在左侧选择数据库类型,输入信息,点击生成</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === LoadingState.LOADING && (
|
||||||
|
<div className="h-full flex flex-col items-center justify-center text-indigo-500">
|
||||||
|
<div className="animate-pulse flex flex-col items-center">
|
||||||
|
<div className="h-2.5 bg-indigo-200 rounded-full w-48 mb-4"></div>
|
||||||
|
<div className="h-2 bg-indigo-100 rounded-full w-32 mb-2.5"></div>
|
||||||
|
<div className="h-2 bg-indigo-100 rounded-full w-40"></div>
|
||||||
|
<span className="mt-6 text-sm font-medium text-slate-500">正在分析表结构并构建 {databaseType} 查询...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === LoadingState.SUCCESS && (
|
||||||
|
<div className="relative group">
|
||||||
|
<pre className="block p-4 rounded-lg bg-slate-900 text-slate-50 font-mono text-sm leading-relaxed whitespace-pre-wrap shadow-inner border border-slate-700">
|
||||||
|
<code>{generatedSql}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === LoadingState.ERROR && (
|
||||||
|
<div className="h-full flex items-center justify-center text-red-400">
|
||||||
|
<p>生成失败,请重试。</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
21
README.md
21
README.md
@@ -1,3 +1,20 @@
|
|||||||
# ai-app-sql
|
<div align="center">
|
||||||
|
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||||
|
</div>
|
||||||
|
|
||||||
使用 Google AI Studio 构建的SQL生成工具——SQL Translate Pro
|
# Run and deploy your AI Studio app
|
||||||
|
|
||||||
|
This contains everything you need to run your app locally.
|
||||||
|
|
||||||
|
View your app in AI Studio: https://ai.studio/apps/drive/1ofeEEpnincTenGsYbd-peK7s-ltU-f1p
|
||||||
|
|
||||||
|
## Run Locally
|
||||||
|
|
||||||
|
**Prerequisites:** Node.js
|
||||||
|
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
`npm install`
|
||||||
|
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||||
|
3. Run the app:
|
||||||
|
`npm run dev`
|
||||||
|
|||||||
39
components/Button.tsx
Normal file
39
components/Button.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
variant?: 'primary' | 'secondary' | 'outline';
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: React.FC<ButtonProps> = ({
|
||||||
|
children,
|
||||||
|
variant = 'primary',
|
||||||
|
isLoading = false,
|
||||||
|
className = '',
|
||||||
|
disabled,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const baseStyle = "inline-flex items-center justify-center px-4 py-2 border text-sm font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed";
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
primary: "border-transparent text-white bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500",
|
||||||
|
secondary: "border-transparent text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:ring-indigo-500",
|
||||||
|
outline: "border-gray-300 text-gray-700 bg-white hover:bg-gray-50 focus:ring-indigo-500"
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`${baseStyle} ${variants[variant]} ${className}`}
|
||||||
|
disabled={disabled || isLoading}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-current" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
40
components/Select.tsx
Normal file
40
components/Select.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
||||||
|
label: string;
|
||||||
|
options: { value: string; label: string }[];
|
||||||
|
helperText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Select: React.FC<SelectProps> = ({ label, options, helperText, className = '', ...props }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<div className="flex justify-between items-baseline">
|
||||||
|
<label className="block text-sm font-semibold text-gray-700">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{helperText && (
|
||||||
|
<span className="text-xs text-gray-500 italic">{helperText}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
className={`block w-full appearance-none rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2.5 pr-8 border bg-white text-gray-800 ${className}`}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{options.map((opt) => (
|
||||||
|
<option key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-500">
|
||||||
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
25
components/TextArea.tsx
Normal file
25
components/TextArea.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface TextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||||
|
label: string;
|
||||||
|
helperText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextArea: React.FC<TextAreaProps> = ({ label, helperText, className = '', ...props }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1.5 h-full">
|
||||||
|
<div className="flex justify-between items-baseline">
|
||||||
|
<label className="block text-sm font-semibold text-gray-700">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{helperText && (
|
||||||
|
<span className="text-xs text-gray-500 italic">{helperText}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
className={`flex-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-3 border resize-none font-mono bg-white text-gray-800 ${className}`}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
46
index.html
Normal file
46
index.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>SQL Translate Pro</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
/* Custom scrollbar for better aesthetics */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"react/": "https://aistudiocdn.com/react@^19.2.1/",
|
||||||
|
"react": "https://aistudiocdn.com/react@^19.2.1",
|
||||||
|
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.1/",
|
||||||
|
"@google/genai": "https://aistudiocdn.com/@google/genai@^1.31.0",
|
||||||
|
"vite": "https://aistudiocdn.com/vite@^7.2.6",
|
||||||
|
"@vitejs/plugin-react": "https://aistudiocdn.com/@vitejs/plugin-react@^5.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-slate-50 text-slate-900 h-screen overflow-hidden">
|
||||||
|
<div id="root" class="h-full"></div>
|
||||||
|
<script type="module" src="/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
index.tsx
Normal file
15
index.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error("Could not find root element to mount to");
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
5
metadata.json
Normal file
5
metadata.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "SQL Translate Pro",
|
||||||
|
"description": "智能SQL生成器:根据提供的表结构、字典表和业务需求,自动生成包含字典翻译逻辑的复杂SQL查询语句。",
|
||||||
|
"requestFramePermissions": []
|
||||||
|
}
|
||||||
22
package.json
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "sql-translate-pro",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"start": "npm run build && vite preview --port 8080 --host"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@google/genai": "*",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"@types/node": "^20.12.7",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"typescript": "^5.4.5",
|
||||||
|
"vite": "^5.2.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
releases/WORK-SH-XPC-SQL-0.1.0_20251209.zip
Normal file
BIN
releases/WORK-SH-XPC-SQL-0.1.0_20251209.zip
Normal file
Binary file not shown.
58
services/gemini.ts
Normal file
58
services/gemini.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
import { GoogleGenAI } from "@google/genai";
|
||||||
|
import { SqlGenerationRequest } from "../types";
|
||||||
|
|
||||||
|
const generateSqlPrompt = (data: SqlGenerationRequest): string => {
|
||||||
|
return `
|
||||||
|
你是一名世界级的数据库架构师和SQL专家。请根据以下提供的信息,编写一个精确、高效的 SQL 查询语句。
|
||||||
|
|
||||||
|
### 1. 目标数据库类型
|
||||||
|
**${data.databaseType}**
|
||||||
|
(请严格遵守该数据库的方言规范,包括引号使用、函数名称、分页语法、字符串连接方式等)
|
||||||
|
|
||||||
|
### 2. 业务表结构与字段说明
|
||||||
|
${data.tableStructure}
|
||||||
|
|
||||||
|
### 3. 字典表信息 (用于代码转义)
|
||||||
|
${data.dictionaryData}
|
||||||
|
|
||||||
|
### 4. 查询需求
|
||||||
|
${data.requirement}
|
||||||
|
|
||||||
|
### 任务要求:
|
||||||
|
1. **自动关联**:根据表结构和字典表,自动构建 JOIN 语句。
|
||||||
|
2. **字典翻译**:需求中提到的字段如果需要字典翻译(例如:政治面貌、国籍等),请务必关联字典表,取出对应的中文名称(Label/Value)。
|
||||||
|
3. **输出格式**:只返回一段纯净的 SQL 代码,不需要 markdown 标记(如 \`\`\`sql),不需要解释文字。
|
||||||
|
4. **命名规范**:使用清晰的表别名(Alias),例如 main_table 为 t1, dict_table 为 d1 等。
|
||||||
|
5. **语法兼容**:针对 **${data.databaseType}** 进行优化。例如:
|
||||||
|
- 如果是 Oracle,请注意字段通常大写,使用双引号处理特殊列名,日期处理使用 TO_DATE/TO_CHAR。
|
||||||
|
- 如果是 MySQL,使用反引号 (\`) 处理列名。
|
||||||
|
- 如果是 SQL Server,使用 [] 处理列名,注意 TOP 语法。
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateSql = async (requestData: SqlGenerationRequest): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
|
||||||
|
|
||||||
|
// Using gemini-2.5-flash for speed and good reasoning capabilities on coding tasks
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-2.5-flash',
|
||||||
|
contents: generateSqlPrompt(requestData),
|
||||||
|
config: {
|
||||||
|
thinkingConfig: { thinkingBudget: 0 }, // Disable thinking for faster direct response
|
||||||
|
temperature: 0.2, // Lower temperature for more deterministic code generation
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let sql = response.text || '';
|
||||||
|
|
||||||
|
// Clean up potential markdown formatting if the model adds it despite instructions
|
||||||
|
sql = sql.replace(/^```sql\n/, '').replace(/^```\n/, '').replace(/\n```$/, '');
|
||||||
|
|
||||||
|
return sql.trim();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error generating SQL:", error);
|
||||||
|
throw new Error("生成 SQL 失败,请检查 API Key 或网络连接。");
|
||||||
|
}
|
||||||
|
};
|
||||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
||||||
22
types.ts
Normal file
22
types.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
export type DatabaseType = 'MySQL' | 'PostgreSQL' | 'Oracle' | 'SQL Server' | 'SQLite' | 'Hive' | 'Dm';
|
||||||
|
|
||||||
|
export interface SqlGenerationRequest {
|
||||||
|
tableStructure: string;
|
||||||
|
dictionaryData: string;
|
||||||
|
requirement: string;
|
||||||
|
databaseType: DatabaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HistoryItem {
|
||||||
|
id: string;
|
||||||
|
timestamp: number;
|
||||||
|
sql: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LoadingState {
|
||||||
|
IDLE = 'IDLE',
|
||||||
|
LOADING = 'LOADING',
|
||||||
|
SUCCESS = 'SUCCESS',
|
||||||
|
ERROR = 'ERROR',
|
||||||
|
}
|
||||||
21
vite.config.ts
Normal file
21
vite.config.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, '.', '');
|
||||||
|
return {
|
||||||
|
plugins: [react()],
|
||||||
|
define: {
|
||||||
|
'process.env.API_KEY': JSON.stringify(process.env.API_KEY || env.API_KEY)
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 8080,
|
||||||
|
host: '0.0.0.0'
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
port: 8080,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
allowedHosts: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user