deneve07 commited on
Commit
0207f8d
·
verified ·
1 Parent(s): a429c13

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -122
app.py CHANGED
@@ -1,5 +1,4 @@
1
  import re
2
- import datetime
3
  import requests
4
  from urllib.parse import quote
5
  from bs4 import BeautifulSoup
@@ -7,219 +6,256 @@ from playwright.sync_api import sync_playwright
7
  import gradio as gr
8
  import os
9
 
10
- # 強制安裝 Playwright 瀏覽器核心 (若在 HF Spaces 執行)
11
  os.system("playwright install chromium")
12
 
13
- # ==========================================
14
- # 工具函數:英文成分自動翻譯為日文片假名
15
- # ==========================================
16
  def translate_en_to_ja(text):
17
  try:
18
  url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=ja&dt=t&q={quote(text)}"
19
  res = requests.get(url, timeout=5)
20
  if res.status_code == 200:
21
  return res.json()[0][0][0].strip()
22
- except:
23
- pass
24
- return text # 若翻譯失敗則回傳原字串
25
 
26
  # ==========================================
27
- # 🇬🇧 英國 eMC 原廠查詢
28
  # ==========================================
29
  def get_uk_originator(ingredient_query, page):
30
- print(f"[英國] 搜尋中: {ingredient_query}")
 
 
 
31
  try:
 
32
  page.goto(f"https://www.medicines.org.uk/emc/search?q={ingredient_query}", timeout=30000)
 
 
33
  page.wait_for_selector('.search-results-product-info-title-link', timeout=15000)
34
 
35
  soup = BeautifulSoup(page.content(), 'html.parser')
 
 
36
 
37
- # 尋找所有商品名的 <a> 連結
38
- for link in soup.find_all('a', class_='search-results-product-info-title-link'):
39
  raw_title = link.get_text(strip=True)
40
-
41
- # 如果標題不是以成分名開頭,就是原廠商品名 (例如 Ilaxten)
42
  if not raw_title.lower().startswith(ingredient_query.lower()):
43
  clean_brand = re.split(r'\s+\d', raw_title)[0].strip()
 
44
 
45
- company = "-"
46
- # 往上找父層,再往下找公司名稱的 div
47
  parent_div = link.find_parent(class_='search-results-product-info')
48
  if parent_div:
49
  comp_tag = parent_div.find(class_='search-results-product-info-company')
50
- if comp_tag: company = comp_tag.get_text(strip=True)
51
-
52
- return clean_brand, company
53
-
54
- return "查無原廠 (可能皆為學名藥)", "-"
 
 
 
 
55
  except Exception as e:
56
- return "查無資料", "-"
 
57
 
58
  # ==========================================
59
- # 🇺🇸 美國 FDA Orange Book 原廠查詢
60
  # ==========================================
61
  def get_usa_originator(ingredient_query, page):
62
- print(f"[美國] 搜尋中: {ingredient_query}")
 
 
 
63
  try:
 
64
  page.goto("https://www.accessdata.fda.gov/scripts/cder/ob/index.cfm", timeout=30000)
65
- page.click('button:has-text("Active Ingredient"), a:has-text("Active Ingredient")')
66
- page.fill('input[name="activeIngredient"], input[id*="ingredient"]', ingredient_query)
67
- page.click('button[id*="submit"], input[type="submit"]')
68
 
69
- # 等待 id="example" 的表格出現
 
 
 
 
 
 
70
  page.wait_for_selector('table#example', timeout=15000)
71
- soup = BeautifulSoup(page.content(), 'html.parser')
72
 
 
73
  table = soup.find('table', id='example')
 
74
  if table:
75
- # 尋找表頭找出各欄位正確的 Index
76
  headers = [th.get_text(strip=True).lower() for th in table.find_all('th')]
77
  brand_idx = next((i for i, h in enumerate(headers) if 'proprietary name' in h), 2)
78
  rld_idx = next((i for i, h in enumerate(headers) if 'rld' in h), 8)
79
  mfg_idx = next((i for i, h in enumerate(headers) if 'applicant holder' in h), 10)
80
 
81
- # 解析表格內容
82
  tbody = table.find('tbody') or table
83
- for tr in tbody.find_all('tr'):
 
 
 
84
  tds = tr.find_all('td')
85
  if len(tds) > max(rld_idx, brand_idx):
86
- # 判斷 RLD 欄位文字是否包含 "RLD"
87
- if "rld" in tds[rld_idx].get_text(strip=True).lower():
88
- brand = tds[brand_idx].get_text(strip=True)
89
- company = tds[mfg_idx].get_text(strip=True) if len(tds) > mfg_idx else "-"
90
- return brand, company
91
-
92
- return "查無原廠或尚未核准", "-"
 
 
 
 
 
 
 
93
  except Exception as e:
94
- return "查無資料 (可能無此藥)", "-"
 
95
 
96
  # ==========================================
97
- # 🇨🇦 加拿大 DPD 原廠查詢
98
  # ==========================================
99
  def get_canada_originator(ingredient_query, page):
100
- print(f"[加拿大] 搜尋中: {ingredient_query}")
101
- earliest_date = datetime.datetime.now()
102
- originator_brand, company_name = None, "-"
103
- generic_companies = ['apotex', 'teva', 'sandoz', 'jamp', 'mint', 'pharmascience', 'sanis', 'sivem', 'auro', 'glenmark']
104
 
105
  try:
 
106
  page.goto("https://health-products.canada.ca/dpd-bdpp/index-eng.jsp", timeout=30000)
107
- page.fill('input[id="activeIngredient"]', ingredient_query)
108
- page.click('input[type="submit"][value*="Search"]')
109
 
110
- # 等待 id="results" 的表格
 
 
 
 
111
  page.wait_for_selector('table#results', timeout=15000)
112
- soup = BeautifulSoup(page.content(), 'html.parser')
113
 
114
- candidate_links = []
115
  table = soup.find('table', id='results')
116
 
117
  if table:
118
  tbody = table.find('tbody')
119
  if tbody:
120
- for tr in tbody.find_all('tr'):
 
 
 
121
  tds = tr.find_all('td')
122
  if len(tds) >= 4:
123
- # 藥廠在第 3 欄 (index 2)
124
  comp_name_full = tds[2].get_text(strip=True)
125
  comp_name_lower = comp_name_full.lower()
126
 
127
- # 排除學名藥廠
128
- if any(gc in comp_name_lower for gc in generic_companies):
129
- continue
130
-
131
- # 連結在第 2 欄 (index 1) 的 <a> 標籤內
132
- link_tag = tds[1].find('a')
133
- if link_tag:
134
- full_url = "https://health-products.canada.ca" + link_tag['href']
135
- # 商品名在第 4 欄 (index 3)
136
  product_name = tds[3].get_text(strip=True)
137
- candidate_links.append({"url": full_url, "company": comp_name_full, "product": product_name})
138
-
139
- # 進入詳細頁面比對最初上市日期
140
- for item in candidate_links[:3]:
141
- page.goto(item['url'], timeout=15000)
142
- detail_soup = BeautifulSoup(page.content(), 'html.parser')
143
- strong_tag = detail_soup.find(lambda tag: tag.name == "strong" and "Original Market Authorization Date" in tag.get_text())
144
- if strong_tag and strong_tag.next_sibling:
145
- try:
146
- auth_date = datetime.datetime.strptime(strong_tag.next_sibling.strip(), "%Y-%m-%d")
147
- if auth_date < earliest_date:
148
- earliest_date = auth_date
149
- originator_brand = item['product']
150
- company_name = item['company']
151
- except: pass
152
-
153
- return originator_brand if originator_brand else "查無原廠", company_name
154
  except Exception as e:
155
- return "查無資料", "-"
 
156
 
157
  # ==========================================
158
- # 🇯🇵 日本 PMDA 原廠查詢
159
  # ==========================================
160
  def get_japan_originator(ingredient_query_ja, page):
161
- print(f"[日本] 搜尋中: {ingredient_query_ja}")
 
 
 
 
162
  try:
 
163
  page.goto("https://www.pmda.go.jp/PmdaSearch/iyakuSearch/", timeout=30000)
164
- page.fill('input[title*="一般名"], input[name="generalName"]', ingredient_query_ja)
165
- page.click('input[type="submit"][value*="検索"]')
166
 
167
- # 等待 id="ResultList" 的表格
168
- page.wait_for_selector('table#ResultList', timeout=15000)
169
- soup = BeautifulSoup(page.content(), 'html.parser')
170
 
 
 
 
 
 
 
 
 
171
  table = soup.find('table', id='ResultList')
 
172
  if table:
173
- # 略過第一、第二列的表頭 (tr有2個是th)
174
- for tr in table.find_all('tr'):
 
175
  tds = tr.find_all('td')
176
  if len(tds) >= 3:
177
- # 抓取第 2 欄 (index 1) 的商品名
178
  raw_brand = tds[1].get_text(strip=True)
179
-
180
- # 排除帶有「」或()的學名藥,且不能包含純成分名
181
  if '「' not in raw_brand and '(' not in raw_brand and ingredient_query_ja not in raw_brand:
182
- clean_brand = re.split(r'(|カプセル|顆粒|シロップ|OD|細粒|液|\d+)', raw_brand)[0].strip()
183
-
184
- # 抓取第 3 欄 (index 2) 的廠商名
185
- company_name = tds[2].get_text(separator=" ", strip=True).replace('製造販売元/', '')
186
- return clean_brand, company_name
187
-
188
- return "查無原廠 (可能皆為學名藥)", "-"
 
 
 
 
 
 
 
189
  except Exception as e:
190
- return "查無資料", "-"
 
191
 
192
  # ==========================================
193
  # 🚀 主執行函數
194
  # ==========================================
195
- def run_all_countries(ingredient_en):
196
  if not ingredient_en:
197
- return [["錯誤", "請輸入英文成分名", "-"]]
198
 
199
- # 翻譯為日文
200
- ingredient_ja = translate_en_to_ja(ingredient_en)
201
- print(f"成分解析: 英文={ingredient_en}, 日文={ingredient_ja}")
 
 
202
 
203
  results = []
204
 
205
  with sync_playwright() as p:
206
- # 一個 Browser instance 加快速度
207
  browser = p.chromium.launch(headless=True, args=['--no-sandbox', '--disable-dev-shm-usage'])
208
  context = browser.new_context()
209
  page = context.new_page()
210
 
211
- # 依序執行四查詢
212
- uk_brand, uk_mfg = get_uk_originator(ingredient_en, page)
213
- results.append(["🇬🇧 英國 (eMC)", uk_brand, uk_mfg])
214
 
215
- us_brand, us_mfg = get_usa_originator(ingredient_en, page)
216
- results.append(["🇺🇸 美國 (FDA)", us_brand, us_mfg])
 
217
 
218
- ca_brand, ca_mfg = get_canada_originator(ingredient_en, page)
219
- results.append(["🇨🇦 加拿大 (DPD)", ca_brand, ca_mfg])
 
220
 
221
- ja_brand, ja_mfg = get_japan_originator(ingredient_ja, page)
222
- results.append(["🇯🇵 日本 (PMDA)", ja_brand, ja_mfg])
 
223
 
224
  browser.close()
225
 
@@ -228,21 +264,23 @@ def run_all_countries(ingredient_en):
228
  # ==========================================
229
  # 🎨 UI 介面
230
  # ==========================================
231
- with gr.Blocks(title="四國原廠商品名智能檢索") as demo:
232
- gr.Markdown("## 🌐 跨國原廠商品名自動檢索器")
233
- gr.Markdown("只需輸入 **英文成分名**,系統會自動翻譯日文,並同步爬取英、美、加、日四國官方資料庫,智慧判斷原廠商品名。")
234
 
235
  with gr.Row():
236
- ing_input = gr.Textbox(label="🧪 請輸入英文成分名 (Active Ingredient)", placeholder="例如: Bilastine")
237
- search_btn = gr.Button("🚀 一鍵查詢四國原廠", variant="primary")
238
 
 
 
239
  result_table = gr.Dataframe(
240
- headers=["國家 / 資料庫", "🌟 判定為原廠的商品名", "🏭 藥廠名稱"],
241
- datatype=["str", "str", "str"],
 
242
  interactive=False
243
  )
244
 
245
- search_btn.click(fn=run_all_countries, inputs=[ing_input], outputs=[result_table])
246
 
247
  if __name__ == "__main__":
248
  demo.launch()
 
1
  import re
 
2
  import requests
3
  from urllib.parse import quote
4
  from bs4 import BeautifulSoup
 
6
  import gradio as gr
7
  import os
8
 
 
9
  os.system("playwright install chromium")
10
 
 
 
 
11
  def translate_en_to_ja(text):
12
  try:
13
  url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=ja&dt=t&q={quote(text)}"
14
  res = requests.get(url, timeout=5)
15
  if res.status_code == 200:
16
  return res.json()[0][0][0].strip()
17
+ except Exception as e:
18
+ return f"翻譯失敗: {e}"
19
+ return text
20
 
21
  # ==========================================
22
+ # 🇬🇧 英國 eMC (支援多重商品名)
23
  # ==========================================
24
  def get_uk_originator(ingredient_query, page):
25
+ log = []
26
+ brands = set()
27
+ companies = set()
28
+
29
  try:
30
+ log.append("1. 前往 eMC 搜尋頁面...")
31
  page.goto(f"https://www.medicines.org.uk/emc/search?q={ingredient_query}", timeout=30000)
32
+
33
+ log.append("2. 等待搜尋結果...")
34
  page.wait_for_selector('.search-results-product-info-title-link', timeout=15000)
35
 
36
  soup = BeautifulSoup(page.content(), 'html.parser')
37
+ links = soup.find_all('a', class_='search-results-product-info-title-link')
38
+ log.append(f"3. 找到 {len(links)} 筆結果,開始篩選...")
39
 
40
+ for link in links:
 
41
  raw_title = link.get_text(strip=True)
 
 
42
  if not raw_title.lower().startswith(ingredient_query.lower()):
43
  clean_brand = re.split(r'\s+\d', raw_title)[0].strip()
44
+ brands.add(clean_brand)
45
 
 
 
46
  parent_div = link.find_parent(class_='search-results-product-info')
47
  if parent_div:
48
  comp_tag = parent_div.find(class_='search-results-product-info-company')
49
+ if comp_tag: companies.add(comp_tag.get_text(strip=True))
50
+
51
+ if brands:
52
+ log.append("✅ 成功找到原廠藥!")
53
+ return ", ".join(brands), ", ".join(companies), "\n".join(log)
54
+ else:
55
+ log.append("❌ 結果皆以成分名開頭,判定為無原廠。")
56
+ return "查無原廠", "-", "\n".join(log)
57
+
58
  except Exception as e:
59
+ log.append(f" 發生錯誤: {str(e)}")
60
+ return "執行失敗", "-", "\n".join(log)
61
 
62
  # ==========================================
63
+ # 🇺🇸 美國 FDA (支援多重商品名)
64
  # ==========================================
65
  def get_usa_originator(ingredient_query, page):
66
+ log = []
67
+ brands = set()
68
+ companies = set()
69
+
70
  try:
71
+ log.append("1. 前往 FDA Orange Book...")
72
  page.goto("https://www.accessdata.fda.gov/scripts/cder/ob/index.cfm", timeout=30000)
 
 
 
73
 
74
+ log.append("2. 切換至 Active Ingredient 頁籤並輸入...")
75
+ # 增強版定位器
76
+ page.locator('a[aria-controls="ingredient"], button:has-text("Active Ingredient")').first.click()
77
+ page.locator('input[name="activeIngredient"], input#ingredient').first.fill(ingredient_query)
78
+ page.keyboard.press("Enter") # 避免按鈕找不到,直接按 Enter
79
+
80
+ log.append("3. 等待結果表格...")
81
  page.wait_for_selector('table#example', timeout=15000)
 
82
 
83
+ soup = BeautifulSoup(page.content(), 'html.parser')
84
  table = soup.find('table', id='example')
85
+
86
  if table:
 
87
  headers = [th.get_text(strip=True).lower() for th in table.find_all('th')]
88
  brand_idx = next((i for i, h in enumerate(headers) if 'proprietary name' in h), 2)
89
  rld_idx = next((i for i, h in enumerate(headers) if 'rld' in h), 8)
90
  mfg_idx = next((i for i, h in enumerate(headers) if 'applicant holder' in h), 10)
91
 
 
92
  tbody = table.find('tbody') or table
93
+ rows = tbody.find_all('tr')
94
+ log.append(f"4. 表格載入完成,共 {len(rows)} 列資料,開始尋找 RLD...")
95
+
96
+ for tr in rows:
97
  tds = tr.find_all('td')
98
  if len(tds) > max(rld_idx, brand_idx):
99
+ rld_text = tds[rld_idx].get_text(strip=True).upper()
100
+ # 只要欄位內有 RLD 三個字就是原廠
101
+ if "RLD" in rld_text:
102
+ brands.add(tds[brand_idx].get_text(strip=True))
103
+ if len(tds) > mfg_idx:
104
+ companies.add(tds[mfg_idx].get_text(strip=True))
105
+
106
+ if brands:
107
+ log.append(f"✅ 成功找到 {len(brands)} 個 RLD 原廠藥!")
108
+ return ", ".join(brands), ", ".join(companies), "\n".join(log)
109
+ else:
110
+ log.append("❌ 表格中未發現 RLD 標記。")
111
+ return "尚未核准或無 RLD", "-", "\n".join(log)
112
+
113
  except Exception as e:
114
+ log.append(f" 發生錯誤: {str(e)}")
115
+ return "執行失敗", "-", "\n".join(log)
116
 
117
  # ==========================================
118
+ # 🇨🇦 加拿大 DPD (支援多重商品名)
119
  # ==========================================
120
  def get_canada_originator(ingredient_query, page):
121
+ log = []
122
+ brands = set()
123
+ companies = set()
124
+ generic_companies = ['apotex', 'teva', 'sandoz', 'jamp', 'mint', 'pharmascience', 'sanis', 'sivem', 'auro', 'glenmark', 'taro']
125
 
126
  try:
127
+ log.append("1. 前往 Canada DPD...")
128
  page.goto("https://health-products.canada.ca/dpd-bdpp/index-eng.jsp", timeout=30000)
 
 
129
 
130
+ log.append("2. 輸入成分並送...")
131
+ page.locator('input[id="activeIngredient"]').fill(ingredient_query)
132
+ page.keyboard.press("Enter")
133
+
134
+ log.append("3. 等待結果表格...")
135
  page.wait_for_selector('table#results', timeout=15000)
 
136
 
137
+ soup = BeautifulSoup(page.content(), 'html.parser')
138
  table = soup.find('table', id='results')
139
 
140
  if table:
141
  tbody = table.find('tbody')
142
  if tbody:
143
+ rows = tbody.find_all('tr')
144
+ log.append(f"4. 找到 {len(rows)} 筆資料,過濾學名藥廠中...")
145
+
146
+ for tr in rows:
147
  tds = tr.find_all('td')
148
  if len(tds) >= 4:
 
149
  comp_name_full = tds[2].get_text(strip=True)
150
  comp_name_lower = comp_name_full.lower()
151
 
152
+ # 如果不是知名學名藥廠,我們就將其視為原廠(收集起來)
153
+ if not any(gc in comp_name_lower for gc in generic_companies):
 
 
 
 
 
 
 
154
  product_name = tds[3].get_text(strip=True)
155
+ brands.add(product_name)
156
+ companies.add(comp_name_full)
157
+
158
+ if brands:
159
+ log.append("✅ 成功過濾出非學名藥品項!")
160
+ return ", ".join(brands), ", ".join(companies), "\n".join(log)
161
+ else:
162
+ log.append("❌ 剩下的全為學名藥廠,查無原廠。")
163
+ return "查無原廠", "-", "\n".join(log)
164
+
 
 
 
 
 
 
 
165
  except Exception as e:
166
+ log.append(f" 發生錯誤: {str(e)}")
167
+ return "執行失敗", "-", "\n".join(log)
168
 
169
  # ==========================================
170
+ # 🇯🇵 日本 PMDA (支援多重商品名)
171
  # ==========================================
172
  def get_japan_originator(ingredient_query_ja, page):
173
+ log = []
174
+ brands = set()
175
+ companies = set()
176
+
177
+ log.append(f"使用日文名: {ingredient_query_ja} 進行搜尋")
178
  try:
179
+ log.append("1. 前往 PMDA...")
180
  page.goto("https://www.pmda.go.jp/PmdaSearch/iyakuSearch/", timeout=30000)
 
 
181
 
182
+ log.append("2. 輸入並送...")
183
+ page.locator('input[title*="一般名"], input[name="generalName"]').fill(ingredient_query_ja)
184
+ page.keyboard.press("Enter")
185
 
186
+ log.append("3. 等待表格 id=ResultList...")
187
+ try:
188
+ page.wait_for_selector('table#ResultList', timeout=15000)
189
+ except:
190
+ log.append("❌ 等待逾時,可能是完全查無此成分。")
191
+ return "查無資料", "-", "\n".join(log)
192
+
193
+ soup = BeautifulSoup(page.content(), 'html.parser')
194
  table = soup.find('table', id='ResultList')
195
+
196
  if table:
197
+ rows = table.find_all('tr')
198
+ log.append(f"4. 找到表格,分析 {len(rows)} 列資料...")
199
+ for tr in rows:
200
  tds = tr.find_all('td')
201
  if len(tds) >= 3:
 
202
  raw_brand = tds[1].get_text(strip=True)
203
+ # 條件:無括號學名標記,且不能只是純成分名
 
204
  if '「' not in raw_brand and '(' not in raw_brand and ingredient_query_ja not in raw_brand:
205
+ # 切除皮下注、OD、數字等劑量標籤
206
+ clean_brand = re.split(r'(皮下注|錠|カプセル|顆粒|シロップ|OD|細粒|液|\d+)', raw_brand)[0].strip()
207
+ if clean_brand:
208
+ brands.add(clean_brand)
209
+ comp = tds[2].get_text(separator=" ", strip=True).replace('製造販売元/', '')
210
+ companies.add(comp)
211
+
212
+ if brands:
213
+ log.append("✅ 成功排除學名藥括號,找到原廠!")
214
+ return ", ".join(brands), ", ".join(companies), "\n".join(log)
215
+ else:
216
+ log.append("❌ 找到的都是學名藥或格式不符。")
217
+ return "查無原廠", "-", "\n".join(log)
218
+
219
  except Exception as e:
220
+ log.append(f" 發生錯誤: {str(e)}")
221
+ return "執行失敗", "-", "\n".join(log)
222
 
223
  # ==========================================
224
  # 🚀 主執行函數
225
  # ==========================================
226
+ def run_diagnostic_search(ingredient_en, ingredient_ja_manual):
227
  if not ingredient_en:
228
+ return [["錯誤", "請輸入英文成分名", "-", ""]]
229
 
230
+ # 如果使用者沒有手填寫日文,就呼叫 API 翻譯
231
+ if not ingredient_ja_manual:
232
+ ingredient_ja = translate_en_to_ja(ingredient_en)
233
+ else:
234
+ ingredient_ja = ingredient_ja_manual
235
 
236
  results = []
237
 
238
  with sync_playwright() as p:
239
+ # 使Firefox 測試 (有時候 Chromium 會被反爬蟲擋住)
240
  browser = p.chromium.launch(headless=True, args=['--no-sandbox', '--disable-dev-shm-usage'])
241
  context = browser.new_context()
242
  page = context.new_page()
243
 
244
+ #
245
+ uk_b, uk_c, uk_log = get_uk_originator(ingredient_en, page)
246
+ results.append(["🇬🇧 英國 (eMC)", uk_b, uk_c, uk_log])
247
 
248
+ # 美國
249
+ us_b, us_c, us_log = get_usa_originator(ingredient_en, page)
250
+ results.append(["🇺🇸 美國 (FDA)", us_b, us_c, us_log])
251
 
252
+ # 加拿大
253
+ ca_b, ca_c, ca_log = get_canada_originator(ingredient_en, page)
254
+ results.append(["🇨🇦 加拿大 (DPD)", ca_b, ca_c, ca_log])
255
 
256
+ # 日本
257
+ ja_b, ja_c, ja_log = get_japan_originator(ingredient_ja, page)
258
+ results.append(["🇯🇵 日本 (PMDA)", ja_b, ja_c, ja_log])
259
 
260
  browser.close()
261
 
 
264
  # ==========================================
265
  # 🎨 UI 介面
266
  # ==========================================
267
+ with gr.Blocks(title="四國原廠智能檢索 (診斷與多重版)") as demo:
268
+ gr.Markdown("## 🌐 跨國原廠商品名檢索器 (支援多重商品名與診斷紀錄)")
 
269
 
270
  with gr.Row():
271
+ ing_input = gr.Textbox(label="🧪 英文成分名 (必填)", placeholder="例如: Semaglutide")
272
+ ja_input = gr.Textbox(label="🇯🇵 日文成分名 (選填,若空白則自動翻譯)", placeholder="例如: セマグルチド (若自動翻譯失敗請手動填入)")
273
 
274
+ search_btn = gr.Button("🚀 啟動診斷與查詢", variant="primary")
275
+
276
  result_table = gr.Dataframe(
277
+ headers=["國家", "🌟 判定為原廠的商品名", "🏭 藥廠名稱", "🛠️ 系統執行診斷日誌"],
278
+ datatype=["str", "str", "str", "str"],
279
+ wrap=True, # 讓長長的日誌可以自動換行
280
  interactive=False
281
  )
282
 
283
+ search_btn.click(fn=run_diagnostic_search, inputs=[ing_input, ja_input], outputs=[result_table])
284
 
285
  if __name__ == "__main__":
286
  demo.launch()