Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import requests
|
| 3 |
+
import os
|
| 4 |
+
from google import genai
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
# --- 設定 API 金鑰 ---
|
| 8 |
+
def setup_api_key():
|
| 9 |
+
"""
|
| 10 |
+
設定 Google API 金鑰。
|
| 11 |
+
"""
|
| 12 |
+
api_key = os.environ.get('GOOGLE_API_KEY')
|
| 13 |
+
if not api_key or api_key == "YOUR_API_KEY_HERE":
|
| 14 |
+
print("警告:環境變數 'GOOGLE_API_KEY' 未設定或無效。AI 功能將無法使用。")
|
| 15 |
+
return False
|
| 16 |
+
else:
|
| 17 |
+
print("成功從環境變數讀取 GOOGLE_API_KEY。")
|
| 18 |
+
return True
|
| 19 |
+
|
| 20 |
+
api_available = setup_api_key()
|
| 21 |
+
client = None
|
| 22 |
+
if api_available:
|
| 23 |
+
try:
|
| 24 |
+
client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print(f"初始化 Google API Client 時發生錯誤: {e}")
|
| 27 |
+
api_available = False
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# --- 元素資料定義 ---
|
| 31 |
+
ELEMENTS_DATA = {
|
| 32 |
+
# 這裡省略了完整的元素資料,以節省空間,實際程式碼應包含所有元素
|
| 33 |
+
"H": {"name": "氫", "symbol": "H", "number": 1, "period": 1, "group": 1, "category": "nonmetal"},
|
| 34 |
+
"He": {"name": "氦", "symbol": "He", "number": 2, "period": 1, "group": 18, "category": "noble_gas"},
|
| 35 |
+
"Li": {"name": "鋰", "symbol": "Li", "number": 3, "period": 2, "group": 1, "category": "alkali_metal"},
|
| 36 |
+
"Be": {"name": "鈹", "symbol": "Be", "number": 4, "period": 2, "group": 2, "category": "alkaline_earth"},
|
| 37 |
+
"B": {"name": "硼", "symbol": "B", "number": 5, "period": 2, "group": 13, "category": "metalloid"},
|
| 38 |
+
"C": {"name": "碳", "symbol": "C", "number": 6, "period": 2, "group": 14, "category": "nonmetal"},
|
| 39 |
+
"N": {"name": "氮", "symbol": "N", "number": 7, "period": 2, "group": 15, "category": "nonmetal"},
|
| 40 |
+
"O": {"name": "氧", "symbol": "O", "number": 8, "period": 2, "group": 16, "category": "nonmetal"},
|
| 41 |
+
"F": {"name": "氟", "symbol": "F", "number": 9, "period": 2, "group": 17, "category": "halogen"},
|
| 42 |
+
"Ne": {"name": "氖", "symbol": "Ne", "number": 10, "period": 2, "group": 18, "category": "noble_gas"},
|
| 43 |
+
"Na": {"name": "鈉", "symbol": "Na", "number": 11, "period": 3, "group": 1, "category": "alkali_metal"},
|
| 44 |
+
"Mg": {"name": "鎂", "symbol": "Mg", "number": 12, "period": 3, "group": 2, "category": "alkaline_earth"},
|
| 45 |
+
"Al": {"name": "鋁", "symbol": "Al", "number": 13, "period": 3, "group": 13, "category": "post_transition"},
|
| 46 |
+
"Si": {"name": "矽", "symbol": "Si", "number": 14, "period": 3, "group": 14, "category": "metalloid"},
|
| 47 |
+
"P": {"name": "磷", "symbol": "P", "number": 15, "period": 3, "group": 15, "category": "nonmetal"},
|
| 48 |
+
"S": {"name": "硫", "symbol": "S", "number": 16, "period": 3, "group": 16, "category": "nonmetal"},
|
| 49 |
+
"Cl": {"name": "氯", "symbol": "Cl", "number": 17, "period": 3, "group": 17, "category": "halogen"},
|
| 50 |
+
"Ar": {"name": "氬", "symbol": "Ar", "number": 18, "period": 3, "group": 18, "category": "noble_gas"},
|
| 51 |
+
"K": {"name": "鉀", "symbol": "K", "number": 19, "period": 4, "group": 1, "category": "alkali_metal"},
|
| 52 |
+
"Ca": {"name": "鈣", "symbol": "Ca", "number": 20, "period": 4, "group": 2, "category": "alkaline_earth"},
|
| 53 |
+
"Sc": {"name": "鈧", "symbol": "Sc", "number": 21, "period": 4, "group": 3, "category": "transition"},
|
| 54 |
+
"Ti": {"name": "鈦", "symbol": "Ti", "number": 22, "period": 4, "group": 4, "category": "transition"},
|
| 55 |
+
"V": {"name": "釩", "symbol": "V", "number": 23, "period": 4, "group": 5, "category": "transition"},
|
| 56 |
+
"Cr": {"name": "鉻", "symbol": "Cr", "number": 24, "period": 4, "group": 6, "category": "transition"},
|
| 57 |
+
"Mn": {"name": "錳", "symbol": "Mn", "number": 25, "period": 4, "group": 7, "category": "transition"},
|
| 58 |
+
"Fe": {"name": "鐵", "symbol": "Fe", "number": 26, "period": 4, "group": 8, "category": "transition"},
|
| 59 |
+
"Co": {"name": "鈷", "symbol": "Co", "number": 27, "period": 4, "group": 9, "category": "transition"},
|
| 60 |
+
"Ni": {"name": "鎳", "symbol": "Ni", "number": 28, "period": 4, "group": 10, "category": "transition"},
|
| 61 |
+
"Cu": {"name": "銅", "symbol": "Cu", "number": 29, "period": 4, "group": 11, "category": "transition"},
|
| 62 |
+
"Zn": {"name": "鋅", "symbol": "Zn", "number": 30, "period": 4, "group": 12, "category": "transition"},
|
| 63 |
+
"Ga": {"name": "鎵", "symbol": "Ga", "number": 31, "period": 4, "group": 13, "category": "post_transition"},
|
| 64 |
+
"Ge": {"name": "鍺", "symbol": "Ge", "number": 32, "period": 4, "group": 14, "category": "metalloid"},
|
| 65 |
+
"As": {"name": "砷", "symbol": "As", "number": 33, "period": 4, "group": 15, "category": "metalloid"},
|
| 66 |
+
"Se": {"name": "硒", "symbol": "Se", "number": 34, "period": 4, "group": 16, "category": "nonmetal"},
|
| 67 |
+
"Br": {"name": "溴", "symbol": "Br", "number": 35, "period": 4, "group": 17, "category": "halogen"},
|
| 68 |
+
"Kr": {"name": "氪", "symbol": "Kr", "number": 36, "period": 4, "group": 18, "category": "noble_gas"},
|
| 69 |
+
"Rb": {"name": "銣", "symbol": "Rb", "number": 37, "period": 5, "group": 1, "category": "alkali_metal"},
|
| 70 |
+
"Sr": {"name": "鍶", "symbol": "Sr", "number": 38, "period": 5, "group": 2, "category": "alkaline_earth"},
|
| 71 |
+
"Y": {"name": "釔", "symbol": "Y", "number": 39, "period": 5, "group": 3, "category": "transition"},
|
| 72 |
+
"Zr": {"name": "鋯", "symbol": "Zr", "number": 40, "period": 5, "group": 4, "category": "transition"},
|
| 73 |
+
"Nb": {"name": "鈮", "symbol": "Nb", "number": 41, "period": 5, "group": 5, "category": "transition"},
|
| 74 |
+
"Mo": {"name": "鉬", "symbol": "Mo", "number": 42, "period": 5, "group": 6, "category": "transition"},
|
| 75 |
+
"Tc": {"name": "鎝", "symbol": "Tc", "number": 43, "period": 5, "group": 7, "category": "transition"},
|
| 76 |
+
"Ru": {"name": "釕", "symbol": "Ru", "number": 44, "period": 5, "group": 8, "category": "transition"},
|
| 77 |
+
"Rh": {"name": "銠", "symbol": "Rh", "number": 45, "period": 5, "group": 9, "category": "transition"},
|
| 78 |
+
"Pd": {"name": "鈀", "symbol": "Pd", "number": 46, "period": 5, "group": 10, "category": "transition"},
|
| 79 |
+
"Ag": {"name": "銀", "symbol": "Ag", "number": 47, "period": 5, "group": 11, "category": "transition"},
|
| 80 |
+
"Cd": {"name": "鎘", "symbol": "Cd", "number": 48, "period": 5, "group": 12, "category": "transition"},
|
| 81 |
+
"In": {"name": "銦", "symbol": "In", "number": 49, "period": 5, "group": 13, "category": "post_transition"},
|
| 82 |
+
"Sn": {"name": "錫", "symbol": "Sn", "number": 50, "period": 5, "group": 14, "category": "post_transition"},
|
| 83 |
+
"Sb": {"name": "銻", "symbol": "Sb", "number": 51, "period": 5, "group": 15, "category": "metalloid"},
|
| 84 |
+
"Te": {"name": "碲", "symbol": "Te", "number": 52, "period": 5, "group": 16, "category": "metalloid"},
|
| 85 |
+
"I": {"name": "碘", "symbol": "I", "number": 53, "period": 5, "group": 17, "category": "halogen"},
|
| 86 |
+
"Xe": {"name": "氙", "symbol": "Xe", "number": 54, "period": 5, "group": 18, "category": "noble_gas"},
|
| 87 |
+
"Cs": {"name": "銫", "symbol": "Cs", "number": 55, "period": 6, "group": 1, "category": "alkali_metal"},
|
| 88 |
+
"Ba": {"name": "鋇", "symbol": "Ba", "number": 56, "period": 6, "group": 2, "category": "alkaline_earth"},
|
| 89 |
+
"La": {"name": "鑭", "symbol": "La", "number": 57, "period": 6, "group": 3, "category": "lanthanide"},
|
| 90 |
+
"Hf": {"name": "鉿", "symbol": "Hf", "number": 72, "period": 6, "group": 4, "category": "transition"},
|
| 91 |
+
"Ta": {"name": "鉭", "symbol": "Ta", "number": 73, "period": 6, "group": 5, "category": "transition"},
|
| 92 |
+
"W": {"name": "鎢", "symbol": "W", "number": 74, "period": 6, "group": 6, "category": "transition"},
|
| 93 |
+
"Re": {"name": "錸", "symbol": "Re", "number": 75, "period": 6, "group": 7, "category": "transition"},
|
| 94 |
+
"Os": {"name": "鋨", "symbol": "Os", "number": 76, "period": 6, "group": 8, "category": "transition"},
|
| 95 |
+
"Ir": {"name": "銥", "symbol": "Ir", "number": 77, "period": 6, "group": 9, "category": "transition"},
|
| 96 |
+
"Pt": {"name": "鉑", "symbol": "Pt", "number": 78, "period": 6, "group": 10, "category": "transition"},
|
| 97 |
+
"Au": {"name": "金", "symbol": "Au", "number": 79, "period": 6, "group": 11, "category": "transition"},
|
| 98 |
+
"Hg": {"name": "汞", "symbol": "Hg", "number": 80, "period": 6, "group": 12, "category": "transition"},
|
| 99 |
+
"Tl": {"name": "鉈", "symbol": "Tl", "number": 81, "period": 6, "group": 13, "category": "post_transition"},
|
| 100 |
+
"Pb": {"name": "鉛", "symbol": "Pb", "number": 82, "period": 6, "group": 14, "category": "post_transition"},
|
| 101 |
+
"Bi": {"name": "鉍", "symbol": "Bi", "number": 83, "period": 6, "group": 15, "category": "post_transition"},
|
| 102 |
+
"Po": {"name": "釙", "symbol": "Po", "number": 84, "period": 6, "group": 16, "category": "post_transition"},
|
| 103 |
+
"At": {"name": "砈", "symbol": "At", "number": 85, "period": 6, "group": 17, "category": "halogen"},
|
| 104 |
+
"Rn": {"name": "氡", "symbol": "Rn", "number": 86, "period": 6, "group": 18, "category": "noble_gas"},
|
| 105 |
+
"Fr": {"name": "鍅", "symbol": "Fr", "number": 87, "period": 7, "group": 1, "category": "alkali_metal"},
|
| 106 |
+
"Ra": {"name": "鐳", "symbol": "Ra", "number": 88, "period": 7, "group": 2, "category": "alkaline_earth"},
|
| 107 |
+
"Ac": {"name": "錒", "symbol": "Ac", "number": 89, "period": 7, "group": 3, "category": "actinide"},
|
| 108 |
+
"Rf": {"name": "鑪", "symbol": "Rf", "number": 104, "period": 7, "group": 4, "category": "transition"},
|
| 109 |
+
"Db": {"name": "𨧀", "symbol": "Db", "number": 105, "period": 7, "group": 5, "category": "transition"},
|
| 110 |
+
"Sg": {"name": "𨭎", "symbol": "Sg", "number": 106, "period": 7, "group": 6, "category": "transition"},
|
| 111 |
+
"Bh": {"name": "𨨏", "symbol": "Bh", "number": 107, "period": 7, "group": 7, "category": "transition"},
|
| 112 |
+
"Hs": {"name": "𨭆", "symbol": "Hs", "number": 108, "period": 7, "group": 8, "category": "transition"},
|
| 113 |
+
"Mt": {"name": "䥑", "symbol": "Mt", "number": 109, "period": 7, "group": 9, "category": "transition"},
|
| 114 |
+
"Ds": {"name": "𨭌", "symbol": "Ds", "number": 110, "period": 7, "group": 10, "category": "transition"},
|
| 115 |
+
"Rg": {"name": "錀", "symbol": "Rg", "number": 111, "period": 7, "group": 11, "category": "transition"},
|
| 116 |
+
"Cn": {"name": "鎶", "symbol": "Cn", "number": 112, "period": 7, "group": 12, "category": "transition"},
|
| 117 |
+
"Nh": {"name": "鉨", "symbol": "Nh", "number": 113, "period": 7, "group": 13, "category": "post_transition"},
|
| 118 |
+
"Fl": {"name": "鈇", "symbol": "Fl", "number": 114, "period": 7, "group": 14, "category": "post_transition"},
|
| 119 |
+
"Mc": {"name": "鏌", "symbol": "Mc", "number": 115, "period": 7, "group": 15, "category": "post_transition"},
|
| 120 |
+
"Lv": {"name": "鉝", "symbol": "Lv", "number": 116, "period": 7, "group": 16, "category": "post_transition"},
|
| 121 |
+
"Ts": {"name": "鿬", "symbol": "Ts", "number": 117, "period": 7, "group": 17, "category": "halogen"},
|
| 122 |
+
"Og": {"name": "鿫", "symbol": "Og", "number": 118, "period": 7, "group": 18, "category": "noble_gas"},
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
# 顏色配置
|
| 126 |
+
CATEGORY_COLORS = {
|
| 127 |
+
"alkali_metal": "#FF6B6B", "alkaline_earth": "#FFA726", "transition": "#42A5F5",
|
| 128 |
+
"post_transition": "#66BB6A", "metalloid": "#AB47BC", "nonmetal": "#FFCA28",
|
| 129 |
+
"halogen": "#26C6DA", "noble_gas": "#EC407A", "lanthanide": "#8D6E63", "actinide": "#78909C",
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
# --- 核心函式 ---
|
| 133 |
+
def get_element_image(symbol):
|
| 134 |
+
"""獲取元素圖片"""
|
| 135 |
+
image_url = f"https://www.webelements.com/_media/elements/element_pictures/{symbol}.jpg"
|
| 136 |
+
try:
|
| 137 |
+
response = requests.head(image_url, timeout=5)
|
| 138 |
+
response.raise_for_status()
|
| 139 |
+
return image_url
|
| 140 |
+
except requests.exceptions.RequestException as e:
|
| 141 |
+
print(f"獲取圖片時發生錯誤: {e}")
|
| 142 |
+
# 維持使用者指定的版本
|
| 143 |
+
return "https://dummyimage.com/400x400/2c3e50/ecf0f1.png&text=image+not+found"
|
| 144 |
+
|
| 145 |
+
def get_element_trivia(element_name):
|
| 146 |
+
"""使用 Gemini API 生成元素冷知識"""
|
| 147 |
+
if not client:
|
| 148 |
+
return "錯誤:Google API 金鑰未設定或無效,無法生成 AI 資訊。"
|
| 149 |
+
|
| 150 |
+
try:
|
| 151 |
+
prompt = (
|
| 152 |
+
f"請你扮演一位化學知識淵博的科學家,用生動有趣的方式介紹化學元素「{element_name}」。"
|
| 153 |
+
"請包含以下幾點,並用繁體中文回答:\n"
|
| 154 |
+
"1. **一個最驚人的冷知識**:提供一個最讓人意想不到的有趣事實。\n"
|
| 155 |
+
"2. **發現與命名**:簡述這個元素的發現歷史或命名由來。\n"
|
| 156 |
+
"3. **生活中的應用**:舉出 2-3 個它在日常生活、科技或工業上的重要應用。\n"
|
| 157 |
+
"4. **對人體的影響**:說明它對人體是必需、有毒、還是無關。\n"
|
| 158 |
+
"請讓內容兼具知識性與趣味性,排版清晰易讀,適合一般大眾閱讀。"
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# 維持使用者指定的版本
|
| 162 |
+
response = client.models.generate_content(
|
| 163 |
+
model="gemini-2.0-flash-lite",
|
| 164 |
+
contents=prompt
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
return response.text
|
| 168 |
+
except Exception as e:
|
| 169 |
+
print(f"呼叫 Gemini API 時發生錯誤: {e}")
|
| 170 |
+
return f"無法生成關於「{element_name}」的資訊,請檢查您的 API 金鑰設定或稍後再試。"
|
| 171 |
+
|
| 172 |
+
def create_periodic_table_html():
|
| 173 |
+
"""創建互動式元素週期表 HTML"""
|
| 174 |
+
|
| 175 |
+
table_layout = [
|
| 176 |
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2],
|
| 177 |
+
[3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 8, 9, 10],
|
| 178 |
+
[11, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 14, 15, 16, 17, 18],
|
| 179 |
+
[19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36],
|
| 180 |
+
[37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54],
|
| 181 |
+
[55, 56, 57, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86],
|
| 182 |
+
[87, 88, 89, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118],
|
| 183 |
+
]
|
| 184 |
+
|
| 185 |
+
number_to_element = {data["number"]: symbol for symbol, data in ELEMENTS_DATA.items()}
|
| 186 |
+
|
| 187 |
+
html = """
|
| 188 |
+
<style>
|
| 189 |
+
.periodic-table { display: grid; grid-template-columns: repeat(18, 1fr); gap: 2px; padding: 10px; border-radius: 15px; }
|
| 190 |
+
.element-cell { aspect-ratio: 1; border: 1px solid #FFFFFF44; border-radius: 8px; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; font-family: 'Arial', sans-serif; font-weight: bold; color: white; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); position: relative; overflow: hidden; }
|
| 191 |
+
.element-cell:hover { transform: scale(1.1); box-shadow: 0 5px 15px rgba(0,0,0,0.4); z-index: 10; }
|
| 192 |
+
.element-cell.selected { border: 2px solid #FFD700; box-shadow: 0 0 20px rgba(255,215,0,0.8); }
|
| 193 |
+
.element-symbol { font-size: 1.2vw; font-weight: bold; margin-bottom: 2px; }
|
| 194 |
+
.element-number { font-size: 0.7vw; position: absolute; top: 4px; left: 4px; }
|
| 195 |
+
.element-name { font-size: 0.7vw; margin-top: 2px; }
|
| 196 |
+
.empty-cell { background: transparent; border: none; cursor: default; }
|
| 197 |
+
.empty-cell:hover { transform: none; box-shadow: none; }
|
| 198 |
+
.legend { margin-top: 20px; padding: 15px; background: rgba(0,0,0,0.2); border-radius: 10px; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
|
| 199 |
+
.legend-item { display: flex; align-items: center; gap: 5px; padding: 5px 10px; border-radius: 5px; color: white; }
|
| 200 |
+
.legend-color { width: 20px; height: 20px; border-radius: 3px; border: 1px solid #FFF; }
|
| 201 |
+
.title { text-align: center; color: white; font-size: 28px; font-weight: bold; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); }
|
| 202 |
+
@media (max-width: 1200px) { .element-symbol { font-size: 1.5vw; } .element-number, .element-name { font-size: 1vw; } }
|
| 203 |
+
@media (max-width: 768px) { .element-symbol { font-size: 2vw; } .element-number, .element-name { font-size: 1.5vw; } }
|
| 204 |
+
</style>
|
| 205 |
+
<!-- <div class="title">🧪 元素週期表 🧪</div> -->
|
| 206 |
+
<div class="periodic-table" id="periodic-table-container">
|
| 207 |
+
"""
|
| 208 |
+
|
| 209 |
+
for row in table_layout:
|
| 210 |
+
for atomic_number in row:
|
| 211 |
+
if atomic_number == 0:
|
| 212 |
+
html += '<div class="element-cell empty-cell"></div>'
|
| 213 |
+
else:
|
| 214 |
+
element_symbol = number_to_element.get(atomic_number, "")
|
| 215 |
+
if element_symbol and element_symbol in ELEMENTS_DATA:
|
| 216 |
+
element_data = ELEMENTS_DATA[element_symbol]
|
| 217 |
+
color = CATEGORY_COLORS.get(element_data["category"], "#888888")
|
| 218 |
+
html += f'''
|
| 219 |
+
<div class="element-cell"
|
| 220 |
+
style="background-color: {color};"
|
| 221 |
+
data-symbol="{element_symbol}">
|
| 222 |
+
<div class="element-number">{atomic_number}</div>
|
| 223 |
+
<div class="element-symbol">{element_symbol}</div>
|
| 224 |
+
<div class="element-name">{element_data['name']}</div>
|
| 225 |
+
</div>
|
| 226 |
+
'''
|
| 227 |
+
else:
|
| 228 |
+
html += '<div class="element-cell empty-cell"></div>'
|
| 229 |
+
|
| 230 |
+
html += "</div><div class='legend'>"
|
| 231 |
+
for category, color in CATEGORY_COLORS.items():
|
| 232 |
+
html += f"""
|
| 233 |
+
<div class="legend-item">
|
| 234 |
+
<div class="legend-color" style="background-color: {color};"></div>
|
| 235 |
+
<span>{get_category_chinese(category)}</span>
|
| 236 |
+
</div>
|
| 237 |
+
"""
|
| 238 |
+
html += "</div>"
|
| 239 |
+
return html
|
| 240 |
+
|
| 241 |
+
def get_category_chinese(category):
|
| 242 |
+
"""將英文類別轉換為中文"""
|
| 243 |
+
category_map = {
|
| 244 |
+
"alkali_metal": "鹼金屬", "alkaline_earth": "鹼土金屬", "transition": "過渡金屬",
|
| 245 |
+
"post_transition": "後過渡金屬", "metalloid": "類金屬", "nonmetal": "非金屬",
|
| 246 |
+
"halogen": "鹵素", "noble_gas": "惰性氣體", "lanthanide": "鑭系元素", "actinide": "錒系元素",
|
| 247 |
+
}
|
| 248 |
+
return category_map.get(category, "未知")
|
| 249 |
+
|
| 250 |
+
# --- 建立 Gradio 介面 ---
|
| 251 |
+
def create_interface():
|
| 252 |
+
"""創建主要的 Gradio 介面"""
|
| 253 |
+
|
| 254 |
+
DEVELOPER_NAME = "陳咨米、阮喆楷、黃聖紘、李依庭"
|
| 255 |
+
|
| 256 |
+
app_description = f"""
|
| 257 |
+
# 🧪 AI 元素週期表冷知識探索器 🧪
|
| 258 |
+
<p style="font-size: 1.2rem; font-weight: bold; margin-top: 20px;margin-bottom: 20px !important;">Designed by: {DEVELOPER_NAME}</p>
|
| 259 |
+
歡迎使用元素週期表冷知識探索器!點擊下方的互動式週期表中任何一個元素,即可查看其照片與 AI 生成的詳細介紹。
|
| 260 |
+
"""
|
| 261 |
+
|
| 262 |
+
js_code = """
|
| 263 |
+
function gradio_init() {
|
| 264 |
+
const observer = new MutationObserver((mutations, obs) => {
|
| 265 |
+
const table = document.getElementById('periodic-table-container');
|
| 266 |
+
if (table) {
|
| 267 |
+
console.log("Periodic table found, attaching event listener.");
|
| 268 |
+
let selectedElementDiv = null;
|
| 269 |
+
|
| 270 |
+
table.addEventListener('click', function(event) {
|
| 271 |
+
const targetCell = event.target.closest('.element-cell');
|
| 272 |
+
|
| 273 |
+
if (targetCell && !targetCell.classList.contains('empty-cell')) {
|
| 274 |
+
const symbol = targetCell.dataset.symbol;
|
| 275 |
+
console.log(`Element clicked: ${symbol}`);
|
| 276 |
+
|
| 277 |
+
if (selectedElementDiv) {
|
| 278 |
+
selectedElementDiv.classList.remove('selected');
|
| 279 |
+
}
|
| 280 |
+
targetCell.classList.add('selected');
|
| 281 |
+
selectedElementDiv = targetCell;
|
| 282 |
+
|
| 283 |
+
const hidden_input_component = document.getElementById('hidden_trigger_for_js');
|
| 284 |
+
if (hidden_input_component) {
|
| 285 |
+
const textarea = hidden_input_component.querySelector('textarea');
|
| 286 |
+
if (textarea) {
|
| 287 |
+
textarea.value = symbol;
|
| 288 |
+
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
});
|
| 293 |
+
obs.disconnect();
|
| 294 |
+
}
|
| 295 |
+
});
|
| 296 |
+
|
| 297 |
+
observer.observe(document.body, {
|
| 298 |
+
childList: true,
|
| 299 |
+
subtree: true
|
| 300 |
+
});
|
| 301 |
+
}
|
| 302 |
+
"""
|
| 303 |
+
|
| 304 |
+
with gr.Blocks(
|
| 305 |
+
theme=gr.themes.Soft(primary_hue="purple", secondary_hue="blue", neutral_hue="gray"),
|
| 306 |
+
css=".gradio-container { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Arial', sans-serif; }",
|
| 307 |
+
js=js_code
|
| 308 |
+
) as app:
|
| 309 |
+
|
| 310 |
+
gr.Markdown(app_description)
|
| 311 |
+
|
| 312 |
+
with gr.Row():
|
| 313 |
+
periodic_table = gr.HTML(value=create_periodic_table_html())
|
| 314 |
+
|
| 315 |
+
with gr.Row():
|
| 316 |
+
with gr.Column(scale=1):
|
| 317 |
+
with gr.Group():
|
| 318 |
+
element_image = gr.Image(
|
| 319 |
+
label="元素照片", height=400, show_label=True,
|
| 320 |
+
value="https://dummyimage.com/400x400/2c3e50/ecf0f1.png&text=click+to+explore+!"
|
| 321 |
+
)
|
| 322 |
+
element_info_html = gr.HTML(
|
| 323 |
+
value="<div style='padding: 10px; text-align: center;'>請選擇一個元素</div>",
|
| 324 |
+
label="📊 元素基本資訊"
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
with gr.Column(scale=1):
|
| 328 |
+
element_trivia = gr.Textbox(
|
| 329 |
+
label="🧠 元素冷知識 (AI 生成)", lines=20,
|
| 330 |
+
interactive=False, placeholder="選擇一個元素來查看詳細資訊..."
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
# 建立一個隱藏的 Textbox,作為 JS 和 Python 之間的橋樑
|
| 334 |
+
hidden_trigger = gr.Textbox(visible=False, elem_id="hidden_trigger_for_js")
|
| 335 |
+
|
| 336 |
+
# 定義處理元素選擇的核心函數
|
| 337 |
+
def process_element_selection(symbol):
|
| 338 |
+
if not symbol or symbol not in ELEMENTS_DATA:
|
| 339 |
+
return (
|
| 340 |
+
# 維持使用者指定的版本
|
| 341 |
+
"https://dummyimage.com/400x400/2c3e50/ecf0f1.png&text=invalid+choice",
|
| 342 |
+
"<div style='padding: 10px; text-align: center;'>請選擇一個有效的元素</div>",
|
| 343 |
+
"請從左側週期表中選擇一個有效的元素。"
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
element_data = ELEMENTS_DATA[symbol]
|
| 347 |
+
name = element_data["name"]
|
| 348 |
+
print(f"正在處理元素: {name} ({symbol})")
|
| 349 |
+
|
| 350 |
+
# 生成基本資訊 HTML
|
| 351 |
+
info_html = f"""
|
| 352 |
+
<div style='padding:10px'>
|
| 353 |
+
<h3 style='color: #007bff; margin-top: 0; margin-bottom: 15px; text-align:center;'>
|
| 354 |
+
{name} ({symbol})
|
| 355 |
+
</h3>
|
| 356 |
+
<div style='display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 14px;'>
|
| 357 |
+
<div><strong>原子序:</strong>{element_data['number']}</div>
|
| 358 |
+
<div><strong>週期:</strong>{element_data['period']}</div>
|
| 359 |
+
<div><strong>族:</strong>{element_data['group']}</div>
|
| 360 |
+
<div><strong>類型:</strong>{get_category_chinese(element_data['category'])}</div>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
"""
|
| 364 |
+
|
| 365 |
+
image_url = get_element_image(symbol)
|
| 366 |
+
trivia_text = get_element_trivia(name)
|
| 367 |
+
|
| 368 |
+
return image_url, info_html, trivia_text
|
| 369 |
+
|
| 370 |
+
# 綁定事件:當隱藏的 Textbox 值改變時,觸發核心處理函數
|
| 371 |
+
hidden_trigger.change(
|
| 372 |
+
fn=process_element_selection,
|
| 373 |
+
inputs=hidden_trigger,
|
| 374 |
+
outputs=[element_image, element_info_html, element_trivia]
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
# 快速範例
|
| 378 |
+
gr.Examples(
|
| 379 |
+
examples=["H", "C", "O", "Cs", "Ca"],
|
| 380 |
+
inputs=hidden_trigger,
|
| 381 |
+
outputs=[element_image, element_info_html, element_trivia],
|
| 382 |
+
fn=process_element_selection,
|
| 383 |
+
cache_examples=False,
|
| 384 |
+
label="快速範例"
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
# 透過 app.load 觸發 JS 初始化函式
|
| 388 |
+
app.load(fn=None, js="gradio_init")
|
| 389 |
+
|
| 390 |
+
return app
|
| 391 |
+
|
| 392 |
+
# 創建並啟動應用程式
|
| 393 |
+
app = create_interface()
|
| 394 |
+
print("🚀 啟動元素週期表探索器...")
|
| 395 |
+
app.launch(debug=False, share=True, show_error=True, show_api=False)
|