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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -81
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import re
2
  import requests
 
3
  from urllib.parse import quote
4
  from bs4 import BeautifulSoup
5
  from playwright.sync_api import sync_playwright
@@ -19,7 +20,7 @@ def translate_en_to_ja(text):
19
  return text
20
 
21
  # ==========================================
22
- # 🇬🇧 英國 eMC (支援多重商品名)
23
  # ==========================================
24
  def get_uk_originator(ingredient_query, page):
25
  log = []
@@ -29,13 +30,11 @@ def get_uk_originator(ingredient_query, page):
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)
@@ -52,15 +51,14 @@ def get_uk_originator(ingredient_query, page):
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 = []
@@ -69,17 +67,16 @@ def get_usa_originator(ingredient_query, page):
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
 
@@ -89,85 +86,108 @@ def get_usa_originator(ingredient_query, page):
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 = []
@@ -179,15 +199,25 @@ def get_japan_originator(ingredient_query_ja, page):
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')
@@ -200,9 +230,7 @@ def get_japan_originator(ingredient_query_ja, page):
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)
@@ -210,10 +238,10 @@ def get_japan_originator(ingredient_query_ja, page):
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:
@@ -221,65 +249,56 @@ def get_japan_originator(ingredient_query_ja, page):
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
-
262
  return results
263
 
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__":
 
1
  import re
2
  import requests
3
+ import datetime
4
  from urllib.parse import quote
5
  from bs4 import BeautifulSoup
6
  from playwright.sync_api import sync_playwright
 
20
  return text
21
 
22
  # ==========================================
23
+ # 🇬🇧 英國 eMC
24
  # ==========================================
25
  def get_uk_originator(ingredient_query, page):
26
  log = []
 
30
  try:
31
  log.append("1. 前往 eMC 搜尋頁面...")
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
  links = soup.find_all('a', class_='search-results-product-info-title-link')
37
+ log.append(f"2. 找到 {len(links)} 筆結果,篩選非成分名開頭的項目...")
38
 
39
  for link in links:
40
  raw_title = link.get_text(strip=True)
 
51
  log.append("✅ 成功找到原廠藥!")
52
  return ", ".join(brands), ", ".join(companies), "\n".join(log)
53
  else:
54
+ log.append("❌ 查無原廠 (皆以成分名開頭)。")
55
  return "查無原廠", "-", "\n".join(log)
 
56
  except Exception as e:
57
  log.append(f"❌ 發生錯誤: {str(e)}")
58
  return "執行失敗", "-", "\n".join(log)
59
 
60
  # ==========================================
61
+ # 🇺🇸 美國 FDA Orange Book (加入偽裝)
62
  # ==========================================
63
  def get_usa_originator(ingredient_query, page):
64
  log = []
 
67
 
68
  try:
69
  log.append("1. 前往 FDA Orange Book...")
70
+ # 加上 wait_until="domcontentloaded" 避免等待外部資源卡住
71
+ page.goto("https://www.accessdata.fda.gov/scripts/cder/ob/index.cfm", timeout=30000, wait_until="domcontentloaded")
72
 
73
+ log.append("2. 切換頁籤並搜尋...")
 
74
  page.locator('a[aria-controls="ingredient"], button:has-text("Active Ingredient")').first.click()
75
  page.locator('input[name="activeIngredient"], input#ingredient').first.fill(ingredient_query)
76
+ page.keyboard.press("Enter")
77
 
78
  log.append("3. 等待結果表格...")
79
  page.wait_for_selector('table#example', timeout=15000)
 
80
  soup = BeautifulSoup(page.content(), 'html.parser')
81
  table = soup.find('table', id='example')
82
 
 
86
  rld_idx = next((i for i, h in enumerate(headers) if 'rld' in h), 8)
87
  mfg_idx = next((i for i, h in enumerate(headers) if 'applicant holder' in h), 10)
88
 
89
+ rows = table.find('tbody').find_all('tr') if table.find('tbody') else table.find_all('tr')
 
 
 
90
  for tr in rows:
91
  tds = tr.find_all('td')
92
  if len(tds) > max(rld_idx, brand_idx):
93
+ if "RLD" in tds[rld_idx].get_text(strip=True).upper():
 
 
94
  brands.add(tds[brand_idx].get_text(strip=True))
95
  if len(tds) > mfg_idx:
96
  companies.add(tds[mfg_idx].get_text(strip=True))
97
 
98
  if brands:
99
+ log.append("✅ 成功找到 RLD 原廠藥!")
100
  return ", ".join(brands), ", ".join(companies), "\n".join(log)
101
  else:
102
+ log.append("❌ 核准或無 RLD。")
103
+ return "原廠", "-", "\n".join(log)
 
104
  except Exception as e:
105
  log.append(f"❌ 發生錯誤: {str(e)}")
106
+ return "執行失敗 (可能被阻擋)", "-", "\n".join(log)
107
 
108
  # ==========================================
109
+ # 🇨🇦 加拿大 DPD (日期比對 + 多重商品名策略)
110
  # ==========================================
111
  def get_canada_originator(ingredient_query, page):
112
  log = []
113
+ # 更新學名藥黑名單以節省比對時間
114
+ generic_companies = ['apotex', 'teva', 'sandoz', 'jamp', 'mint', 'pharmascience', 'sanis', 'sivem', 'auro', 'glenmark', 'taro', 'marcan', 'nora', 'mantra', 'reddy', 'sandoz']
 
115
 
116
  try:
117
  log.append("1. 前往 Canada DPD...")
118
  page.goto("https://health-products.canada.ca/dpd-bdpp/index-eng.jsp", timeout=30000)
 
 
119
  page.locator('input[id="activeIngredient"]').fill(ingredient_query)
120
  page.keyboard.press("Enter")
121
 
122
+ log.append("2. 等待結果表格...")
123
  page.wait_for_selector('table#results', timeout=15000)
 
124
  soup = BeautifulSoup(page.content(), 'html.parser')
125
  table = soup.find('table', id='results')
126
 
127
+ if not table or not table.find('tbody'):
128
+ return "查無資料", "-", "\n".join(log)
129
+
130
+ rows = table.find('tbody').find_all('tr')
131
+ all_candidates = []
132
+ companies_to_check = {} # 記錄每家公司「隨便一個」連結去查日期
133
+
134
+ log.append(f"3. 找到 {len(rows)} 筆,過濾黑名單並彙整藥廠...")
135
+ for tr in rows:
136
+ tds = tr.find_all('td')
137
+ if len(tds) >= 4:
138
+ comp_name = tds[2].get_text(strip=True)
139
+ comp_lower = comp_name.lower()
140
 
141
+ # 擋掉已知學名藥廠
142
+ if any(gc in comp_lower for gc in generic_companies):
143
+ continue
144
+
145
+ product_name = tds[3].get_text(strip=True)
146
+ link_tag = tds[1].find('a')
147
+ if link_tag:
148
+ url = "https://health-products.canada.ca" + link_tag['href']
149
+ all_candidates.append({"company": comp_name, "product": product_name})
150
+ # 每家公司我們只記一個網址進去查日期就好,節省時間!
151
+ if comp_name not in companies_to_check:
152
+ companies_to_check[comp_name] = url
153
+
154
+ if not companies_to_check:
155
+ log.append(" 剩下的全為學名藥廠。")
 
 
156
  return "查無原廠", "-", "\n".join(log)
157
+
158
+ log.append(f"4. 進入詳細頁面比對 {len(companies_to_check)} 家候選藥廠的上市日期...")
159
+ earliest_date = datetime.datetime(2099, 12, 31)
160
+ originator_company = None
161
+
162
+ for comp_name, url in companies_to_check.items():
163
+ try:
164
+ page.goto(url, timeout=15000)
165
+ detail_soup = BeautifulSoup(page.content(), 'html.parser')
166
+ strong_tag = detail_soup.find(lambda tag: tag.name == "strong" and "Original Market Authorization Date" in tag.get_text())
167
+ if strong_tag and strong_tag.next_sibling:
168
+ date_str = strong_tag.next_sibling.strip()
169
+ auth_date = datetime.datetime.strptime(date_str, "%Y-%m-%d")
170
+ log.append(f" - {comp_name}: {date_str}")
171
+ if auth_date < earliest_date:
172
+ earliest_date = auth_date
173
+ originator_company = comp_name
174
+ except Exception as e:
175
+ log.append(f" - {comp_name} 抓取日期失敗")
176
+
177
+ if originator_company:
178
+ log.append(f"✅ 確認最���老原廠為: {originator_company} ({earliest_date.strftime('%Y-%m-%d')})")
179
+ # 把屬於這家原廠的所有商品名都抓出來!
180
+ final_brands = set([c['product'] for c in all_candidates if c['company'] == originator_company])
181
+ return ", ".join(final_brands), originator_company, "\n".join(log)
182
+ else:
183
+ return "查無日期", "-", "\n".join(log)
184
 
185
  except Exception as e:
186
  log.append(f"❌ 發生錯誤: {str(e)}")
187
  return "執行失敗", "-", "\n".join(log)
188
 
189
  # ==========================================
190
+ # 🇯🇵 日本 PMDA (加入自動同意條款)
191
  # ==========================================
192
  def get_japan_originator(ingredient_query_ja, page):
193
  log = []
 
199
  log.append("1. 前往 PMDA...")
200
  page.goto("https://www.pmda.go.jp/PmdaSearch/iyakuSearch/", timeout=30000)
201
 
202
+ # 🟢 破門機制:檢查是否有「同意する」按鈕並點擊
203
+ try:
204
+ agree_btn = page.locator('text=同意する, input[value="同意する"], a:has-text("同意する")').first
205
+ if agree_btn.is_visible(timeout=3000):
206
+ log.append(" - 發現使用條款畫面,自動點擊同意...")
207
+ agree_btn.click()
208
+ page.wait_for_load_state('networkidle')
209
+ except:
210
+ pass # 沒有出現同意畫面就略過
211
+
212
  log.append("2. 輸入並送出...")
213
+ page.locator('input[title*="一般名"], input[name="generalName"]').first.fill(ingredient_query_ja)
214
  page.keyboard.press("Enter")
215
 
216
  log.append("3. 等待表格 id=ResultList...")
217
  try:
218
  page.wait_for_selector('table#ResultList', timeout=15000)
219
  except:
220
+ log.append("❌ 等待逾時,查無此成分。")
221
  return "查無資料", "-", "\n".join(log)
222
 
223
  soup = BeautifulSoup(page.content(), 'html.parser')
 
230
  tds = tr.find_all('td')
231
  if len(tds) >= 3:
232
  raw_brand = tds[1].get_text(strip=True)
 
233
  if '「' not in raw_brand and '(' not in raw_brand and ingredient_query_ja not in raw_brand:
 
234
  clean_brand = re.split(r'(皮下注|錠|カプセル|顆粒|シロップ|OD|細粒|液|\d+)', raw_brand)[0].strip()
235
  if clean_brand:
236
  brands.add(clean_brand)
 
238
  companies.add(comp)
239
 
240
  if brands:
241
+ log.append("✅ 成功找到原廠!")
242
  return ", ".join(brands), ", ".join(companies), "\n".join(log)
243
  else:
244
+ log.append("❌ 皆為學名藥括號。")
245
  return "查無原廠", "-", "\n".join(log)
246
 
247
  except Exception as e:
 
249
  return "執行失敗", "-", "\n".join(log)
250
 
251
  # ==========================================
252
+ # 🚀 主執行函數 (加入 User-Agent 偽裝)
253
  # ==========================================
254
  def run_diagnostic_search(ingredient_en, ingredient_ja_manual):
255
  if not ingredient_en:
256
  return [["錯誤", "請輸入英文成分名", "-", ""]]
257
 
258
+ ingredient_ja = ingredient_ja_manual if ingredient_ja_manual else translate_en_to_ja(ingredient_en)
 
 
 
 
259
 
260
  results = []
 
261
  with sync_playwright() as p:
262
+ # 🟢 加入 user_agent 偽裝成正常的 Chrome,減少的機率
263
  browser = p.chromium.launch(headless=True, args=['--no-sandbox', '--disable-dev-shm-usage'])
264
+ context = browser.new_context(
265
+ user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
266
+ )
267
  page = context.new_page()
268
 
 
269
  uk_b, uk_c, uk_log = get_uk_originator(ingredient_en, page)
270
  results.append(["🇬🇧 英國 (eMC)", uk_b, uk_c, uk_log])
271
 
 
272
  us_b, us_c, us_log = get_usa_originator(ingredient_en, page)
273
  results.append(["🇺🇸 美國 (FDA)", us_b, us_c, us_log])
274
 
 
275
  ca_b, ca_c, ca_log = get_canada_originator(ingredient_en, page)
276
  results.append(["🇨🇦 加拿大 (DPD)", ca_b, ca_c, ca_log])
277
 
 
278
  ja_b, ja_c, ja_log = get_japan_originator(ingredient_ja, page)
279
  results.append(["🇯🇵 日本 (PMDA)", ja_b, ja_c, ja_log])
280
 
281
  browser.close()
 
282
  return results
283
 
284
  # ==========================================
285
  # 🎨 UI 介面
286
  # ==========================================
287
+ with gr.Blocks(title="四國原廠智能檢索 (精準多重版)") as demo:
288
+ gr.Markdown("## 🌐 跨國原廠商品名檢索器 (支援多重商品名與防爬蟲突破)")
289
 
290
  with gr.Row():
291
  ing_input = gr.Textbox(label="🧪 英文成分名 (必填)", placeholder="例如: Semaglutide")
292
+ ja_input = gr.Textbox(label="🇯🇵 日文成分名 (選填)", placeholder="例如: セマグルチド (若空白則自動翻譯)")
293
 
294
+ search_btn = gr.Button("🚀 啟動查詢", variant="primary")
295
 
296
  result_table = gr.Dataframe(
297
  headers=["國家", "🌟 判定為原廠的商品名", "🏭 藥廠名稱", "🛠️ 系統執行診斷日誌"],
298
  datatype=["str", "str", "str", "str"],
299
+ wrap=True,
300
  interactive=False
301
  )
 
302
  search_btn.click(fn=run_diagnostic_search, inputs=[ing_input, ja_input], outputs=[result_table])
303
 
304
  if __name__ == "__main__":