Paul720810 commited on
Commit
fea361d
·
verified ·
1 Parent(s): 6c5a7ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -281
app.py CHANGED
@@ -1,148 +1,103 @@
1
  import gradio as gr
2
- import requests
3
- import json
4
  import os
5
  import re
 
 
 
6
  from datetime import datetime
7
  from datasets import load_dataset
8
  from sentence_transformers import SentenceTransformer, util
9
- import torch
10
  from huggingface_hub import hf_hub_download
 
11
  from typing import List, Dict, Tuple, Optional
12
- import numpy as np
13
-
14
-
15
 
16
  # ==================== 配置區 ====================
17
- HF_TOKEN = os.environ.get("HF_TOKEN", None)
18
  DATASET_REPO_ID = "Paul720810/Text-to-SQL-Softline"
 
 
19
 
20
- # === 修改開始 ===
21
- # 我們不再需要硬性的相似度閾值,因為現在的策略是「參考」而非「直接採用」。
22
- # SIMILARITY_THRESHOLD = 0.65
23
- # 新增一個配置,決定要檢索多少個範例來當作參考
24
- FEW_SHOT_EXAMPLES_COUNT = 2 # 檢索最相似的2個範例
25
- # === 修改結束 ===
26
-
27
- # 雲端環境檢測
28
- IS_SPACES = os.environ.get("SPACE_ID") is not None
29
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
30
 
31
  print("=" * 60)
32
- print("🤖 智能 Text-to-SQL 系統啟動中...")
33
- print(f"📊 模式: 讀取全部數據(來自 {DATASET_REPO_ID}")
34
- print(f"🌐 環境: {'Hugging Face Spaces' if IS_SPACES else '本地環境'}")
35
  print(f"💻 設備: {DEVICE}")
36
  print("=" * 60)
37
 
38
- # ==================== 獨立工具函數 (不依賴類別實例) ====================
39
  def get_current_time():
40
- """獲取當前時間字串"""
41
  return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
42
 
43
  def format_log(message: str, level: str = "INFO") -> str:
44
- """格式化日誌訊息"""
45
  return f"[{get_current_time()}] [{level.upper()}] {message}"
46
 
47
  def parse_sql_from_response(response_text: str) -> Optional[str]:
48
- """從API回應中提取SQL代碼"""
49
  match = re.search(r"```sql\n(.*?)\n```", response_text, re.DOTALL)
50
  if match:
51
  return match.group(1).strip()
52
- # 新增備用解析:如果找不到```sql ...```,直接嘗試解析JSON中的SQL
53
- try:
54
- data = json.loads(response_text)
55
- if "SQL查詢" in data and "```sql" in data["SQL查詢"]:
56
- match = re.search(r"```sql\n(.*?)\n```", data["SQL查詢"], re.DOTALL)
57
- if match:
58
- return match.group(1).strip()
59
- except json.JSONDecodeError:
60
- pass # 不是合法的JSON,忽略
61
  return None
62
 
63
- # ==================== 核心 Text-to-SQL 系統類別 ====================
64
- from transformers import AutoModelForCausalLM, AutoTokenizer
65
-
66
  class TextToSQLSystem:
67
- def __init__(self, model_name='sentence-transformers/paraphrase-multilingual-mpnet-base-v2'):
68
  self.log_history = []
69
  self._log("初始化系統...")
70
 
71
- # 載入檢索模型
72
  self.schema = self._load_schema()
73
- self.model = SentenceTransformer(model_name, device=DEVICE)
 
 
74
  self.dataset, self.corpus_embeddings = self._load_and_encode_dataset()
75
 
76
- # 載入你自己的 Hugging Face 模型
77
- self.generation_model_id = "Paul720810/qwen2.5-coder-1.5b-sql-finetuned"
78
- self.tokenizer = AutoTokenizer.from_pretrained(self.generation_model_id)
79
- self.generation_model = AutoModelForCausalLM.from_pretrained(
80
- self.generation_model_id,
81
- device_map="auto",
82
- torch_dtype="auto"
83
  )
84
-
85
- self._log("✅ 系統初始化完成,已準備就緒。")
86
-
87
- def huggingface_api_call(self, prompt: str) -> str:
88
- """直接使用本地載入的模型生成結果"""
89
- try:
90
- self._log("🧠 開始本地生成 SQL...")
91
-
92
- inputs = self.tokenizer(prompt, return_tensors="pt").to(self.generation_model.device)
93
- outputs = self.generation_model.generate(
94
- **inputs,
95
- max_new_tokens=512,
96
- do_sample=True,
97
- temperature=0.7,
98
- top_p=0.9
99
- )
100
- result = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
101
-
102
- self._log("✅ 本地生成完成。")
103
- return result
104
-
105
- except Exception as e:
106
- self._log(f"❌ 本地生成失敗: {e}", "ERROR")
107
- return f"本地生成錯誤: {e}"
108
 
109
  def _log(self, message: str, level: str = "INFO"):
110
  self.log_history.append(format_log(message, level))
111
  print(format_log(message, level))
112
 
113
  def _load_schema(self) -> Dict:
114
- """從JSON檔案載入資料庫結構"""
115
  try:
116
- schema_path = hf_hub_download(repo_id=DATASET_REPO_ID, filename="sqlite_schema_FULL.json", repo_type="dataset")
117
- with open(schema_path, 'r', encoding='utf-8') as f:
 
 
 
 
118
  self._log("成功載入資料庫結構 (sqlite_schema_FULL.json)")
119
  return json.load(f)
120
  except Exception as e:
121
- self._log(f"❌ 載入資料庫結構失敗: {e}", "ERROR")
122
  return {}
123
-
124
- def _format_schema_for_prompt(self) -> str:
125
- """將 schema JSON 物件格式化為清晰的字串,用於提示"""
126
- formatted_string = "資料庫結構 (Database Schema):\n"
127
- for table_name, columns in self.schema.items():
128
- formatted_string += f"Table: {table_name}\n"
129
- for col in columns:
130
- col_name = col.get('name', 'N/A')
131
- col_type = col.get('type', 'N/A')
132
- col_desc = col.get('description', '')
133
- formatted_string += f" - {col_name} ({col_type}) # {col_desc}\n"
134
- formatted_string += "\n"
135
- return formatted_string
136
 
137
- def _load_and_encode_dataset(self) -> Tuple[Optional[List[Dict]], Optional[torch.Tensor]]:
138
- """載入訓練數據集並對問題進行編碼"""
 
 
 
 
 
 
 
 
139
  try:
140
  dataset = load_dataset(DATASET_REPO_ID, data_files="training_data.jsonl", split="train")
141
-
142
- # 提取所有 "user" 的 "content" 作為語料庫
143
  corpus = [item['messages'][0]['content'] for item in dataset]
144
-
145
- self._log(f"正在對 {len(corpus)} 個範例問題進行編碼...")
146
  embeddings = self.model.encode(corpus, convert_to_tensor=True, device=DEVICE)
147
  self._log("✅ 範例問題編碼完成。")
148
  return dataset, embeddings
@@ -151,212 +106,85 @@ class TextToSQLSystem:
151
  return None, None
152
 
153
  def find_most_similar(self, question: str, top_k: int) -> List[Dict]:
154
- """尋找最相似的K個問題及其對應的SQL"""
155
- if self.corpus_embeddings is None or self.dataset is None:
156
- return []
157
- question_embedding = self.model.encode(question, convert_to_tensor=True, device=DEVICE)
158
- cos_scores = util.cos_sim(question_embedding, self.corpus_embeddings)[0]
159
- top_results = torch.topk(cos_scores, k=min(top_k, len(self.corpus_embeddings)))
160
-
161
- similar_examples = []
162
- for score, idx in zip(top_results[0], top_results[1]):
163
  item = self.dataset[idx.item()]
164
- user_content = item['messages'][0]['content']
165
- assistant_content = item['messages'][1]['content']
166
-
167
- # assistant_content 中提取純 SQL
168
- sql_query = parse_sql_from_response(assistant_content)
169
- if not sql_query:
170
- # 如果解析失敗,可能是格式問題,這裡做個備份
171
- sql_query = "無法解析範例SQL"
172
-
173
- similar_examples.append({
174
- "similarity": score.item(),
175
- "question": user_content,
176
- "sql": sql_query
177
- })
178
- return similar_examples
179
 
180
- # === 修改開始: 重寫核心處理邏輯 ===
181
- def _build_prompt_for_generation(self, user_question: str, examples: List[Dict]) -> str:
182
- """
183
- **新增的函數**
184
- 根據我們的「檢索-增強-生成」策略,建立一個豐富的提示(Prompt)。
185
- """
186
- # 1. 任務指令 (System Instruction)
187
- # 明確告訴 AI 它的角色和目標。
188
  system_instruction = (
189
- "你是一位頂尖的資料庫專家,精通 SQLite。你的任務是根據使用者提出的問題,"
190
- "參考提供的資料庫結構和相似的 SQL 查詢範例,生成一個精確、高效的 SQLite 查詢語法。\n"
191
- "請將最終的 SQL 查詢語法包裝在 ```sql ... ``` 區塊中。"
192
  )
 
 
 
 
 
193
 
194
- # 2. 資料庫結構 (Database Schema)
195
- # 讓 AI 了解有哪些資料表和欄位可用。
196
- schema_string = self._format_schema_for_prompt()
197
-
198
- # 3. 參考範例 (Few-shot Examples)
199
- # 給 AI 看「過去的優良作業」,讓它學習語法風格和邏輯。
200
- examples_string = "--- 參考範例 ---\n"
201
- if not examples:
202
- examples_string += "無\n"
203
- else:
204
- for i, example in enumerate(examples, 1):
205
- # 為了讓提示更清晰,我們只取範例中的 `指令` 部分
206
- clean_question = re.search(r"指令:\s*(.*)", example['question'])
207
- if clean_question:
208
- question_to_show = clean_question.group(1).strip()
209
- else:
210
- question_to_show = example['question'] # 如果格式不符,顯示原文
211
-
212
- examples_string += f"範例 {i}:\n"
213
- examples_string += f" - 使用者問題: \"{question_to_show}\"\n"
214
- examples_string += f" - SQL 查詢:\n```sql\n{example['sql']}\n```\n\n"
215
-
216
- # 4. 新的使用者問題 (User's New Question)
217
- # 這是 AI 這次需要解決的核心問題。
218
- final_question_section = (
219
- "--- 任務開始 ---\n"
220
- f"請根據以上的資料庫結構和參考範例,為以下使用者問題生成 SQL 查詢:\n"
221
- f"使用者問題: \"{user_question}\""
222
- )
223
-
224
- # 組合完整的提示
225
- full_prompt = (
226
- f"{system_instruction}\n\n"
227
- f"{schema_string}\n"
228
- f"{examples_string}"
229
- f"{final_question_section}"
230
- )
231
-
232
- self._log("已建立給 AI 的完整提示 (Prompt):\n" + "="*20 + f"\n{full_prompt}\n" + "="*20)
233
- return full_prompt
234
 
235
  def process_question(self, question: str) -> Tuple[str, str]:
236
- """
237
- 處理使用者問題的核心函數。
238
- 採用「檢索-增強-生成」(RAG) 流程。
239
- """
240
- self.log_history = [] # 清空上次日誌
241
- self._log(f"⏰ 開始處理問題: '{question}'")
242
-
243
- # 步驟 1: 檢索 (Retrieval)
244
- # 無論如何,都先尋找最相似的範例作為參考資料。
245
- self._log(f"🔍 正在從 {len(self.dataset)} 個範例中尋找最相似的 {FEW_SHOT_EXAMPLES_COUNT} 個參考...")
246
- similar_examples = self.find_most_similar(question, top_k=FEW_SHOT_EXAMPLES_COUNT)
247
-
248
- if similar_examples:
249
- for ex in similar_examples:
250
- self._log(f" - 找到相似範例 (相似度: {ex['similarity']:.3f}): '{ex['question'][:50]}...'")
251
- else:
252
- self._log(" - 未找到相似範例。", "WARNING")
253
-
254
- # 步驟 2: 增強 (Augmentation)
255
- # 建立一個包含所有必要資訊的豐富提示。
256
- self._log("📝 正在建立給 AI 的完整提示 (Prompt)...")
257
- prompt = self._build_prompt_for_generation(question, similar_examples)
258
 
259
- # 步驟 3: 生成 (Generation)
260
- # 將判斷權交給 AI,讓它根據完整的上下文生成 SQL。
261
- self._log("🧠 將判斷權交給 AI,開始生成 SQL...")
262
- api_response = self.huggingface_api_call(prompt)
263
-
264
- # 處理並回傳結果
265
- sql_query = parse_sql_from_response(api_response)
266
-
267
- if sql_query:
268
- self._log(f"✅ 成功從 AI 回應中解析出 SQL!")
269
- status = "生成成功"
270
- return sql_query, status
271
- else:
272
- self._log("❌ 未能從 AI 回應中解析出有效的 SQL。", "ERROR")
273
- self._log(f" - AI 原始回應: {api_response}", "DEBUG")
274
- status = "生成失敗"
275
- return f"無法從 AI 的回應中提取 SQL。\n\n原始回應:\n{api_response}", status
276
- # === 修改結束 ===
277
 
 
 
278
 
279
- # ==================== Gradio 介面設定 ====================
280
- text_to_sql_system = None
281
- try:
282
- text_to_sql_system = TextToSQLSystem()
283
- except Exception as e:
284
- print(f"初始化 TextToSQLSystem 失敗: {e}")
285
 
286
- def process_query(question: str) -> Tuple[str, str, str]:
287
- """Gradio 的處理函數"""
288
- if not text_to_sql_system:
289
- error_msg = "系統初始化失敗,無法處理請求。"
290
- return error_msg, "失敗", error_msg
291
-
292
- if not question.strip():
293
- return "", "等待輸入", "請輸入您的問題。"
294
 
295
- sql_result, status = text_to_sql_system.process_question(question)
296
- log_output = "\n".join(text_to_sql_system.log_history)
297
- return sql_result, status, log_output
298
 
299
- # Gradio 介面佈局
300
- with gr.Blocks(theme=gr.themes.Soft(), title="Text-to-SQL 智能查詢系統") as demo:
301
- gr.Markdown("# 📊 Text-to-SQL 智能查詢系統")
302
- gr.Markdown("輸入您的自然語言問題,系統將自動轉換為 SQL 查詢語法。")
 
 
303
 
 
 
304
  with gr.Row():
305
  with gr.Column(scale=2):
306
- question_input = gr.Textbox(
307
- lines=3,
308
- label="💬 您的問題",
309
- placeholder="例如:2024年每月完成了多少份報告?"
310
- )
311
- submit_btn = gr.Button("🚀 生成 SQL", variant="primary")
312
- status_output = gr.Textbox(label="處理狀態", interactive=False)
313
-
314
  with gr.Column(scale=3):
315
- sql_output = gr.Code(label="🤖 生成的 SQL 查詢", language="sql")
 
 
316
 
317
- with gr.Accordion("🔍 顯示詳細處理日誌", open=False):
318
- log_output = gr.Textbox(lines=15, label="日誌", interactive=False)
319
 
320
- # 優化的範例
321
- gr.Examples(
322
- examples=[
323
- "2024年每月完成多少份報告?",
324
- "統計各種評級(Pass/Fail)的分布情況",
325
- "找出總金額最高的10個工作單來自哪些申請方",
326
- "哪些客戶的工作單數量最多?",
327
- "A組昨天完成了多少個測試項目?",
328
- "2024年Q1期間評級為Fail且總金額超過10000的工作單"
329
- ],
330
- inputs=question_input,
331
- label="💡 範例問題 (點擊試用)"
332
- )
333
-
334
- # 綁定事件
335
- submit_btn.click(
336
- fn=process_query,
337
- inputs=[question_input],
338
- outputs=[sql_output, status_output, log_output]
339
- )
340
-
341
- question_input.submit(
342
- fn=process_query,
343
- inputs=[question_input],
344
- outputs=[sql_output, status_output, log_output]
345
- )
346
-
347
  if __name__ == "__main__":
348
- if text_to_sql_system:
349
- print("Gradio 介面啟動中...")
350
-
351
- # 根據環境選擇啟動參數
352
- if IS_SPACES:
353
- # Hugging Face Spaces 環境
354
- print("🌐 在 Hugging Face Spaces 環境中啟動...")
355
- demo.launch(
356
- server_name="0.0.0.0",
357
- server_port=7860,
358
- )
359
- else:
360
- # 本地環境
361
- print("🏠 在本地環境中啟動 ([http://127.0.0.1:7860](http://127.0.0.1:7860))...")
362
- demo.launch()
 
1
  import gradio as gr
 
 
2
  import os
3
  import re
4
+ import json
5
+ import torch
6
+ import numpy as np
7
  from datetime import datetime
8
  from datasets import load_dataset
9
  from sentence_transformers import SentenceTransformer, util
 
10
  from huggingface_hub import hf_hub_download
11
+ from llama_cpp import Llama
12
  from typing import List, Dict, Tuple, Optional
 
 
 
13
 
14
  # ==================== 配置區 ====================
 
15
  DATASET_REPO_ID = "Paul720810/Text-to-SQL-Softline"
16
+ GGUF_REPO_ID = "Paul720810/gguf-models"
17
+ GGUF_FILENAME = "qwen2.5-coder-1.5b-sql-finetuned.q4_k_m.gguf"
18
 
19
+ FEW_SHOT_EXAMPLES_COUNT = 2
 
 
 
 
 
 
 
 
20
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
21
 
22
  print("=" * 60)
23
+ print("🤖 Text-to-SQL (GGUF) 系統啟動中...")
24
+ print(f"📊 數據集: {DATASET_REPO_ID}")
 
25
  print(f"💻 設備: {DEVICE}")
26
  print("=" * 60)
27
 
28
+ # ==================== 工具函數 ====================
29
  def get_current_time():
 
30
  return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
31
 
32
  def format_log(message: str, level: str = "INFO") -> str:
 
33
  return f"[{get_current_time()}] [{level.upper()}] {message}"
34
 
35
  def parse_sql_from_response(response_text: str) -> Optional[str]:
36
+ """從模型輸出提取 SQL"""
37
  match = re.search(r"```sql\n(.*?)\n```", response_text, re.DOTALL)
38
  if match:
39
  return match.group(1).strip()
 
 
 
 
 
 
 
 
 
40
  return None
41
 
42
+ # ==================== Text-to-SQL 核心類 ====================
 
 
43
  class TextToSQLSystem:
44
+ def __init__(self, embed_model='sentence-transformers/paraphrase-multilingual-mpnet-base-v2'):
45
  self.log_history = []
46
  self._log("初始化系統...")
47
 
48
+ # 1. 載入 schema
49
  self.schema = self._load_schema()
50
+
51
+ # 2. 載入檢索模型
52
+ self.model = SentenceTransformer(embed_model, device=DEVICE)
53
  self.dataset, self.corpus_embeddings = self._load_and_encode_dataset()
54
 
55
+ # 3. 載入 GGUF 模型
56
+ model_path = hf_hub_download(
57
+ repo_id=GGUF_REPO_ID,
58
+ filename=GGUF_FILENAME,
59
+ repo_type="dataset"
 
 
60
  )
61
+ self.llm = Llama(
62
+ model_path=model_path,
63
+ n_ctx=4096,
64
+ n_threads=8,
65
+ verbose=False
66
+ )
67
+ self._log(f" 已載入 GGUF 模型: {GGUF_FILENAME}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  def _log(self, message: str, level: str = "INFO"):
70
  self.log_history.append(format_log(message, level))
71
  print(format_log(message, level))
72
 
73
  def _load_schema(self) -> Dict:
 
74
  try:
75
+ schema_path = hf_hub_download(
76
+ repo_id=DATASET_REPO_ID,
77
+ filename="sqlite_schema_FULL.json",
78
+ repo_type="dataset"
79
+ )
80
+ with open(schema_path, "r", encoding="utf-8") as f:
81
  self._log("成功載入資料庫結構 (sqlite_schema_FULL.json)")
82
  return json.load(f)
83
  except Exception as e:
84
+ self._log(f"❌ 載入 schema 失敗: {e}", "ERROR")
85
  return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
+ def _format_schema_for_prompt(self) -> str:
88
+ formatted = "資料庫結構:\n"
89
+ for table, cols in self.schema.items():
90
+ formatted += f"Table: {table}\n"
91
+ for col in cols:
92
+ formatted += f" - {col['name']} ({col['type']}) # {col.get('description','')}\n"
93
+ formatted += "\n"
94
+ return formatted
95
+
96
+ def _load_and_encode_dataset(self):
97
  try:
98
  dataset = load_dataset(DATASET_REPO_ID, data_files="training_data.jsonl", split="train")
 
 
99
  corpus = [item['messages'][0]['content'] for item in dataset]
100
+ self._log(f"正在編碼 {len(corpus)} 個問題...")
 
101
  embeddings = self.model.encode(corpus, convert_to_tensor=True, device=DEVICE)
102
  self._log("✅ 範例問題編碼完成。")
103
  return dataset, embeddings
 
106
  return None, None
107
 
108
  def find_most_similar(self, question: str, top_k: int) -> List[Dict]:
109
+ if self.corpus_embeddings is None: return []
110
+ q_emb = self.model.encode(question, convert_to_tensor=True, device=DEVICE)
111
+ scores = util.cos_sim(q_emb, self.corpus_embeddings)[0]
112
+ top = torch.topk(scores, k=min(top_k, len(self.corpus_embeddings)))
113
+ results = []
114
+ for score, idx in zip(top[0], top[1]):
 
 
 
115
  item = self.dataset[idx.item()]
116
+ q = item['messages'][0]['content']
117
+ a = item['messages'][1]['content']
118
+ sql = parse_sql_from_response(a) or "無法解析範例SQL"
119
+ results.append({"similarity": score.item(), "question": q, "sql": sql})
120
+ return results
 
 
 
 
 
 
 
 
 
 
121
 
122
+ def _build_prompt(self, user_q: str, examples: List[Dict]) -> str:
 
 
 
 
 
 
 
123
  system_instruction = (
124
+ "你是一位資料庫專家,請根據使用者的問題,參考資料庫結構與範例,"
125
+ "生成正確的 SQLite 查詢,並用 ```sql ... ``` 包起來。"
 
126
  )
127
+ schema_str = self._format_schema_for_prompt()
128
+ ex_str = "--- 範例 ---\n"
129
+ for i, ex in enumerate(examples, 1):
130
+ ex_str += f"範例 {i} 問題: {ex['question']}\nSQL:\n```sql\n{ex['sql']}\n```\n\n"
131
+ return f"{system_instruction}\n\n{schema_str}\n{ex_str}\n--- 使用者問題 ---\n{user_q}"
132
 
133
+ def huggingface_api_call(self, prompt: str) -> str:
134
+ try:
135
+ self._log("🧠 使用 GGUF 模型生成 SQL...")
136
+ output = self.llm(prompt, max_tokens=512, stop=["</s>"])
137
+ text = output["choices"][0]["text"]
138
+ return text
139
+ except Exception as e:
140
+ self._log(f"❌ 生成失敗: {e}", "ERROR")
141
+ return f"生成失敗: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  def process_question(self, question: str) -> Tuple[str, str]:
144
+ self.log_history = []
145
+ self._log(f"⏰ 問題: {question}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
+ self._log("🔍 尋找相似範例...")
148
+ examples = self.find_most_similar(question, FEW_SHOT_EXAMPLES_COUNT)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
+ self._log("📝 建立 Prompt...")
151
+ prompt = self._build_prompt(question, examples)
152
 
153
+ self._log("🧠 開始生成...")
154
+ response = self.huggingface_api_call(prompt)
 
 
 
 
155
 
156
+ sql = parse_sql_from_response(response)
157
+ if sql:
158
+ self._log("✅ 成功解析 SQL")
159
+ return sql, "生成成功"
160
+ else:
161
+ self._log("❌ 未能解析 SQL", "ERROR")
162
+ return f"原始回應:\n{response}", "生成失敗"
 
163
 
164
+ # ==================== Gradio 介面 ====================
165
+ text_to_sql_system = TextToSQLSystem()
 
166
 
167
+ def process_query(q: str):
168
+ if not q.strip():
169
+ return "", "等待輸入", "請輸入問題"
170
+ sql, status = text_to_sql_system.process_question(q)
171
+ logs = "\n".join(text_to_sql_system.log_history)
172
+ return sql, status, logs
173
 
174
+ with gr.Blocks(theme=gr.themes.Soft(), title="Text-to-SQL Assistant (GGUF)") as demo:
175
+ gr.Markdown("# 📊 Text-to-SQL Assistant (GGUF)")
176
  with gr.Row():
177
  with gr.Column(scale=2):
178
+ inp = gr.Textbox(lines=3, label="💬 問題")
179
+ btn = gr.Button("🚀 生成 SQL")
180
+ status = gr.Textbox(label="狀態", interactive=False)
 
 
 
 
 
181
  with gr.Column(scale=3):
182
+ sql_out = gr.Code(label="🤖 SQL", language="sql")
183
+ with gr.Accordion("日誌", open=False):
184
+ logs = gr.Textbox(lines=15, label="處理日誌", interactive=False)
185
 
186
+ btn.click(process_query, inputs=[inp], outputs=[sql_out, status, logs])
187
+ inp.submit(process_query, inputs=[inp], outputs=[sql_out, status, logs])
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  if __name__ == "__main__":
190
+ demo.launch(server_name="0.0.0.0", server_port=7860)