diff --git a/App.tsx b/App.tsx index 1f18297..e096878 100644 --- a/App.tsx +++ b/App.tsx @@ -5,6 +5,7 @@ import { Button } from './components/Button'; import { TextArea } from './components/TextArea'; import { Select } from './components/Select'; import { SettingsModal } from './components/SettingsModal'; +import { Toast, ToastType } from './components/Toast'; import { LoadingState, DatabaseType } from './types'; // Default placeholders to help the user understand what to input @@ -49,8 +50,15 @@ const DB_OPTIONS = [ { value: 'SQLite', label: 'SQLite' }, ]; +const MODEL_OPTIONS = [ + { value: 'gemini-3-pro-preview', label: 'Gemini 3.0 Pro (推荐 - 强逻辑)' }, + { value: 'gemini-3-flash-preview', label: 'Gemini 3.0 Flash (极速)' }, + { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' }, +]; + const App: React.FC = () => { const [databaseType, setDatabaseType] = useState('MySQL'); + const [model, setModel] = useState('gemini-3-flash-preview'); const [tableStructure, setTableStructure] = useState(''); const [dictionaryData, setDictionaryData] = useState(''); const [requirement, setRequirement] = useState(''); @@ -63,6 +71,9 @@ const App: React.FC = () => { const [apiKey, setApiKey] = useState(''); const [isSettingsOpen, setIsSettingsOpen] = useState(false); + // Toast Notification State + const [toast, setToast] = useState<{message: string, type: ToastType, id: number} | null>(null); + useEffect(() => { const storedKey = localStorage.getItem('user_api_key'); if (storedKey) { @@ -70,14 +81,22 @@ const App: React.FC = () => { } }, []); + const showToast = (message: string, type: ToastType = 'success') => { + setToast({ message, type, id: Date.now() }); + }; + + const closeToast = () => setToast(null); + const handleSaveApiKey = (key: string) => { setApiKey(key); localStorage.setItem('user_api_key', key); setIsSettingsOpen(false); + showToast("API Key 配置已保存", "success"); }; const handleGenerate = async () => { if (!tableStructure.trim() || !requirement.trim()) { + showToast("请至少填写表结构和查询需求", "error"); setErrorMsg("请至少填写表结构和查询需求。"); return; } @@ -92,24 +111,38 @@ const App: React.FC = () => { dictionaryData, requirement, databaseType, + model, apiKey // Pass the custom API key }); setGeneratedSql(sql); setStatus(LoadingState.SUCCESS); + showToast("SQL 查询生成成功", "success"); } catch (err: any) { setErrorMsg(err.message || "未知错误"); setStatus(LoadingState.ERROR); + showToast(err.message || "生成失败,请重试", "error"); } }; const copyToClipboard = () => { if (generatedSql) { navigator.clipboard.writeText(generatedSql); + showToast("已复制到剪贴板", "success"); } }; return ( -
+
+ {/* Toast Notification Container */} + {toast && ( + + )} + setIsSettingsOpen(false)} @@ -147,13 +180,18 @@ const App: React.FC = () => { {/* Left Panel: Inputs */}
-
+
setModel(e.target.value)} />
@@ -207,7 +245,7 @@ const App: React.FC = () => {
-

生成结果 ({databaseType})

+

生成结果 ({databaseType} by {MODEL_OPTIONS.find(m => m.value === model)?.label?.split('(')[0].trim()})

{status === LoadingState.SUCCESS && (
)} @@ -231,7 +269,7 @@ const App: React.FC = () => {
- 正在分析表结构并构建 {databaseType} 查询... + 正在使用 {MODEL_OPTIONS.find(m => m.value === model)?.label.split('(')[0]} 构建查询...
)} @@ -256,4 +294,4 @@ const App: React.FC = () => { ); }; -export default App; \ No newline at end of file +export default App; diff --git a/components/Select.tsx b/components/Select.tsx index 6edee66..52e8ff6 100644 --- a/components/Select.tsx +++ b/components/Select.tsx @@ -9,18 +9,18 @@ interface SelectProps extends React.SelectHTMLAttributes { export const Select: React.FC = ({ label, options, helperText, className = '', ...props }) => { return ( -
+
-
-
+
-
- - +
+
diff --git a/components/Toast.tsx b/components/Toast.tsx new file mode 100644 index 0000000..59ee2d6 --- /dev/null +++ b/components/Toast.tsx @@ -0,0 +1,64 @@ + +import React, { useEffect, useState } from 'react'; + +export type ToastType = 'success' | 'error' | 'info'; + +export interface ToastProps { + message: string; + type: ToastType; + onClose: () => void; +} + +export const Toast: React.FC = ({ message, type, onClose }) => { + const [visible, setVisible] = useState(false); + + useEffect(() => { + // Start entry animation + requestAnimationFrame(() => setVisible(true)); + + // Auto dismiss + const timer = setTimeout(() => { + setVisible(false); + // Allow exit animation to complete before unmounting + setTimeout(onClose, 300); + }, 3000); + + return () => clearTimeout(timer); + }, [onClose]); + + const baseClasses = "fixed top-6 left-1/2 transform -translate-x-1/2 z-[100] flex items-center gap-3 px-5 py-3 rounded-lg shadow-xl text-white font-medium text-sm transition-all duration-300 ease-out"; + + const typeClasses = { + success: "bg-emerald-600 ring-1 ring-emerald-500", + error: "bg-rose-600 ring-1 ring-rose-500", + info: "bg-indigo-600 ring-1 ring-indigo-500" + }; + + // Animation states + const opacityClass = visible ? "opacity-100 translate-y-0 scale-100" : "opacity-0 -translate-y-4 scale-95"; + + const icons = { + success: ( + + + + ), + error: ( + + + + ), + info: ( + + + + ) + }; + + return ( +
+ {icons[type]} + {message} +
+ ); +}; diff --git a/releases/WORK-SH-XPC-SQL-0.2.0_20251226.zip b/releases/WORK-SH-XPC-SQL-0.2.0_20251226.zip new file mode 100644 index 0000000..c8d559c Binary files /dev/null and b/releases/WORK-SH-XPC-SQL-0.2.0_20251226.zip differ diff --git a/services/gemini.ts b/services/gemini.ts index e6cfd4c..6e7a743 100644 --- a/services/gemini.ts +++ b/services/gemini.ts @@ -42,12 +42,15 @@ export const generateSql = async (requestData: SqlGenerationRequest): Promise