更新至 v0.1.1_20251209 版本

This commit is contained in:
2025-12-09 22:38:05 +08:00
parent 2c7cbac954
commit 4773a9239c
6 changed files with 190 additions and 23 deletions

41
App.tsx
View File

@@ -1,9 +1,10 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { generateSql } from './services/gemini'; import { generateSql } from './services/gemini';
import { Button } from './components/Button'; import { Button } from './components/Button';
import { TextArea } from './components/TextArea'; import { TextArea } from './components/TextArea';
import { Select } from './components/Select'; import { Select } from './components/Select';
import { SettingsModal } from './components/SettingsModal';
import { LoadingState, DatabaseType } from './types'; import { LoadingState, DatabaseType } from './types';
// Default placeholders to help the user understand what to input // Default placeholders to help the user understand what to input
@@ -58,6 +59,23 @@ const App: React.FC = () => {
const [status, setStatus] = useState<LoadingState>(LoadingState.IDLE); const [status, setStatus] = useState<LoadingState>(LoadingState.IDLE);
const [errorMsg, setErrorMsg] = useState<string | null>(null); const [errorMsg, setErrorMsg] = useState<string | null>(null);
// API Key Management
const [apiKey, setApiKey] = useState<string>('');
const [isSettingsOpen, setIsSettingsOpen] = useState<boolean>(false);
useEffect(() => {
const storedKey = localStorage.getItem('user_api_key');
if (storedKey) {
setApiKey(storedKey);
}
}, []);
const handleSaveApiKey = (key: string) => {
setApiKey(key);
localStorage.setItem('user_api_key', key);
setIsSettingsOpen(false);
};
const handleGenerate = async () => { const handleGenerate = async () => {
if (!tableStructure.trim() || !requirement.trim()) { if (!tableStructure.trim() || !requirement.trim()) {
setErrorMsg("请至少填写表结构和查询需求。"); setErrorMsg("请至少填写表结构和查询需求。");
@@ -73,7 +91,8 @@ const App: React.FC = () => {
tableStructure, tableStructure,
dictionaryData, dictionaryData,
requirement, requirement,
databaseType databaseType,
apiKey // Pass the custom API key
}); });
setGeneratedSql(sql); setGeneratedSql(sql);
setStatus(LoadingState.SUCCESS); setStatus(LoadingState.SUCCESS);
@@ -86,12 +105,18 @@ const App: React.FC = () => {
const copyToClipboard = () => { const copyToClipboard = () => {
if (generatedSql) { if (generatedSql) {
navigator.clipboard.writeText(generatedSql); navigator.clipboard.writeText(generatedSql);
// Optional: Show a toast here, but for now we'll just rely on user action
} }
}; };
return ( return (
<div className="flex flex-col h-screen bg-slate-50"> <div className="flex flex-col h-screen bg-slate-50">
<SettingsModal
isOpen={isSettingsOpen}
onClose={() => setIsSettingsOpen(false)}
onSave={handleSaveApiKey}
currentApiKey={apiKey}
/>
{/* Header */} {/* Header */}
<header className="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between shrink-0"> <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="flex items-center gap-3">
@@ -106,7 +131,13 @@ const App: React.FC = () => {
</div> </div>
</div> </div>
<div> <div>
{/* Placeholder for future user settings or profile */} <Button variant="outline" onClick={() => setIsSettingsOpen(true)} className="flex items-center gap-2 text-xs md:text-sm">
<svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
API Key
</Button>
</div> </div>
</header> </header>
@@ -225,4 +256,4 @@ const App: React.FC = () => {
); );
}; };
export default App; export default App;

View File

@@ -1,20 +1,82 @@
<div align="center"> # SQL Translate Pro
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div>
# Run and deploy your AI Studio app **[English]** | [中文] | [日本語]
This contains everything you need to run your app locally. **SQL Translate Pro** is an intelligent SQL generation tool powered by Google Gemini. It helps developers and data analysts quickly build complex SQL queries by analyzing table structures, dictionary definitions, and natural language requirements. It automatically handles table joins and dictionary code translations.
View your app in AI Studio: https://ai.studio/apps/drive/1ofeEEpnincTenGsYbd-peK7s-ltU-f1p ---
## Run Locally ### 🌟 Key Features
**Prerequisites:** Node.js * **Intelligent Generation**: Converts natural language requirements into precise SQL queries.
* **Dictionary Translation**: Automatically identifies code fields (e.g., `status_code`) and joins them with dictionary tables to retrieve readable names.
* **Multi-DB Support**: Optimized for MySQL, PostgreSQL, Oracle, SQL Server, Hive, Dameng (Dm), and SQLite.
* **Privacy Focused**: Your API Key is stored locally in your browser and used directly for API calls.
### 🚀 How to Use
1. Install dependencies: 1. **Select Database**: Choose your target database dialect (e.g., MySQL, Oracle).
`npm install` 2. **Input Schema**: Paste your table DDL or field descriptions.
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key 3. **Input Dictionary**: Describe your dictionary table structure and mapping rules.
3. Run the app: 4. **Describe Requirement**: Type what you want to query in plain text.
`npm run dev` 5. **Generate**: Click the button to get ready-to-use SQL.
6. **Settings**: Click the gear icon in the top right to configure your own Google Gemini API Key.
---
# SQL Translate Pro (中文)
**SQL Translate Pro** 是一款基于 Google Gemini 的智能 SQL 生成工具。它能够根据您提供的表结构、字典表定义以及自然语言查询需求,自动构建复杂的 SQL 查询语句,并智能处理字典代码的翻译(关联查询)。
### 🌟 主要功能
* **智能生成**:将自然语言需求转化为精确的 SQL 语句。
* **字典自动翻译**:自动识别代码字段(如 `nation_code`)并关联字典表取值,无需手动编写繁琐的 JOIN。
* **多数据库支持**:完美支持 MySQL, PostgreSQL, Oracle, SQL Server, Hive, 达梦数据库 (Dm) 和 SQLite 方言。
* **隐私安全**:支持自定义 API KeyKey 仅保存在本地浏览器中,直接用于 API 请求。
### 🚀 使用指南
1. **选择数据库**:在左上角下拉框选择目标数据库类型。
2. **输入表结构**:粘贴建表语句 (DDL) 或字段描述信息。
3. **输入字典信息**:描述字典表的结构以及字段关联规则。
4. **描述需求**:用自然语言描述您想要查询的数据(例如:“查询学生信息及其对应的政治面貌中文名”)。
5. **点击生成**:获取纯净的 SQL 代码。
6. **设置 Key**:点击右上角设置按钮,填入您的 Google Gemini API Key 以获得最佳体验。
---
# SQL Translate Pro (日本語)
**SQL Translate Pro** は、Google Gemini を搭載したインテリジェントな SQL 生成ツールです。テーブル定義、辞書データ、および自然言語の要件に基づいて、辞書変換(コード値の名称変換)を含む複雑な SQL クエリを自動的に生成します。
### 🌟 主な機能
* **インテリジェント生成**: 自然言語の要件を正確な SQL クエリに変換します。
* **辞書自動変換**: コード項目(例:`status_code`)を自動的に特定し、辞書テーブルと結合して名称を取得します。
* **マルチDB対応**: MySQL, PostgreSQL, Oracle, SQL Server, Hive, Dameng (Dm), SQLite に最適化されています。
* **プライバシー重視**: API キーはブラウザにローカル保存され、API 呼び出しに直接使用されます。
### 🚀 使い方
1. **データベース選択**: ターゲットとなるデータベースの種類を選択します。
2. **スキーマ入力**: テーブル作成ステートメント (DDL) またはフィールドの説明を貼り付けます。
3. **辞書情報入力**: 辞書テーブルの構造とマッピングルールを記述します。
4. **要件の記述**: 抽出したいデータを自然言語で入力します。
5. **生成**: ボタンをクリックすると、すぐに使える SQL が生成されます。
6. **設定**: 右上の設定ボタンから、独自の Google Gemini API Key を設定できます。
---
## 🛠 Setup & Development
```bash
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
```

View File

@@ -0,0 +1,65 @@
import React, { useState, useEffect } from 'react';
import { Button } from './Button';
interface SettingsModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (apiKey: string) => void;
currentApiKey: string;
}
export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, onSave, currentApiKey }) => {
const [key, setKey] = useState(currentApiKey);
// Sync internal state when prop changes or modal opens
useEffect(() => {
setKey(currentApiKey);
}, [currentApiKey, isOpen]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm p-4">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-md overflow-hidden transform transition-all">
<div className="bg-slate-50 px-6 py-4 border-b border-slate-100 flex justify-between items-center">
<h3 className="text-lg font-bold text-slate-800"></h3>
<button onClick={onClose} className="text-slate-400 hover:text-slate-600">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="p-6 space-y-4">
<div>
<label htmlFor="apiKey" className="block text-sm font-semibold text-slate-700 mb-2">
Google Gemini API Key
</label>
<input
type="password"
id="apiKey"
value={key}
onChange={(e) => setKey(e.target.value)}
placeholder="AIzaSy..."
className="w-full px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm font-mono"
/>
<p className="mt-2 text-xs text-slate-500">
Key Google Gemini API
<br/>
使
</p>
</div>
</div>
<div className="bg-slate-50 px-6 py-4 border-t border-slate-100 flex justify-end gap-3">
<Button variant="outline" onClick={onClose}>
</Button>
<Button onClick={() => onSave(key)}>
</Button>
</div>
</div>
</div>
);
};

Binary file not shown.

View File

@@ -33,7 +33,14 @@ ${data.requirement}
export const generateSql = async (requestData: SqlGenerationRequest): Promise<string> => { export const generateSql = async (requestData: SqlGenerationRequest): Promise<string> => {
try { try {
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); // Prioritize the user-provided key, fallback to the environment variable
const apiKey = requestData.apiKey || process.env.API_KEY;
if (!apiKey) {
throw new Error("未配置 API Key。请点击右上角设置按钮输入您的 Google Gemini API Key或联系管理员配置环境变量。");
}
const ai = new GoogleGenAI({ apiKey: apiKey });
// Using gemini-2.5-flash for speed and good reasoning capabilities on coding tasks // Using gemini-2.5-flash for speed and good reasoning capabilities on coding tasks
const response = await ai.models.generateContent({ const response = await ai.models.generateContent({
@@ -51,8 +58,9 @@ export const generateSql = async (requestData: SqlGenerationRequest): Promise<st
sql = sql.replace(/^```sql\n/, '').replace(/^```\n/, '').replace(/\n```$/, ''); sql = sql.replace(/^```sql\n/, '').replace(/^```\n/, '').replace(/\n```$/, '');
return sql.trim(); return sql.trim();
} catch (error) { } catch (error: any) {
console.error("Error generating SQL:", error); console.error("Error generating SQL:", error);
throw new Error("生成 SQL 失败,请检查 API Key 或网络连接。"); // Pass through the specific error message if possible
throw new Error(error.message || "生成 SQL 失败,请检查 API Key 或网络连接。");
} }
}; };

View File

@@ -6,6 +6,7 @@ export interface SqlGenerationRequest {
dictionaryData: string; dictionaryData: string;
requirement: string; requirement: string;
databaseType: DatabaseType; databaseType: DatabaseType;
apiKey?: string;
} }
export interface HistoryItem { export interface HistoryItem {
@@ -19,4 +20,4 @@ export enum LoadingState {
LOADING = 'LOADING', LOADING = 'LOADING',
SUCCESS = 'SUCCESS', SUCCESS = 'SUCCESS',
ERROR = 'ERROR', ERROR = 'ERROR',
} }