zenityx's picture
Update app.py
1ff0d0d verified
import math
import gradio as gr
from transformers import MarianTokenizer, MarianMTModel
import re
import functools
###################################
# 1) โหลดโมเดล MarianMT (Thai->En) ครั้งเดียวเมื่อเริ่มแอป
###################################
model_name = "Helsinki-NLP/opus-mt-th-en"
tokenizer = MarianTokenizer.from_pretrained(model_name)
model = MarianMTModel.from_pretrained(model_name)
# เพิ่มการแคชผลลัพธ์การแปล
@functools.lru_cache(maxsize=1024)
def translate_th_to_en(text_th: str) -> str:
"""
แปลไทย -> อังกฤษ ด้วย MarianMT บน CPU
* เรียกเฉพาะส่วนที่ผู้ใช้พิมพ์ เช่น ชื่อดาว, ชื่อสิ่งมีชีวิต
"""
text_th = text_th.strip()
if not text_th:
return ""
inputs = tokenizer(text_th, return_tensors="pt", max_length=512, truncation=True)
translation_tokens = model.generate(**inputs, max_length=512)
en_text = tokenizer.decode(translation_tokens[0], skip_special_tokens=True)
return en_text
###################################
# 2) สูตรอุณหภูมิ (Black Body) + Greenhouse
###################################
def approximate_temp_with_star(star_type, distance_au, albedo=0.3):
"""
star_type: Red Dwarf / White Dwarf / Sun-like / Blue Giant / Supergiant
distance_au: ระยะห่าง (AU)
albedo: สะท้อนแสง (0.3)
"""
lum_ratio_map = {
"Red Dwarf": 0.02,
"White Dwarf": 0.001,
"Sun-like": 1.0,
"Blue Giant": 10.0,
"Supergiant": 100.0
}
lum_ratio = lum_ratio_map.get(star_type, 1.0)
luminosity = 3.828e26 * lum_ratio
dist_m = distance_au * 1.496e11
sigma = 5.67e-8
T_k = ((1 - albedo) * luminosity / (16 * math.pi * sigma * dist_m**2))**0.25
T_c = T_k - 273.15
T_c += 15 # Greenhouse
return round(T_c)
###################################
# 3) สูตรแรงโน้มถ่วง (ตามนิวตัน โดยคำนึงถึงความหนาแน่นตามชนิดดาว)
###################################
def approximate_gravity(diameter_factor, planet_type_th):
"""
คำนวณแรงโน้มถ่วงตามขนาดและชนิดดาว
"""
G = 6.67430e-11 # m³ kg⁻¹ s⁻²
earth_radius = 6.371e6 # meters
radius = diameter_factor * earth_radius # m
# กำหนดความหนาแน่นตามชนิดดาว
density_map = {
"ดาวหิน": 5500, # kg/m³
"ดาวก๊าซ": 1300, # kg/m³
"ดาวน้ำแข็ง": 1600 # kg/m³
}
density = density_map.get(planet_type_th, 5500) # ค่าเริ่มต้นเป็นดาวหิน
# คำนวณมวล: M = density * volume = density * (4/3) * pi * R^3
mass = density * (4/3) * math.pi * radius**3
# คำนวณแรงโน้มถ่วง: g = G * M / R^2
g = G * mass / radius**2 # m/s²
# แปลงเป็น g ของโลก
g_earth = 9.81
g_relative = g / g_earth
return round(g_relative, 2)
###################################
# 4) Dictionary แปลล่วงหน้า (TH->EN)
###################################
pretranslated_map = {
# distance
"โคจรใกล้ดาวฤกษ์มาก": "orbits extremely close to its star",
"โคจรคล้ายโลกหรืออุ่นกว่านิดหน่อย": "orbits similarly to Earth or slightly warmer",
"โคจรห่างพอประมาณ อากาศค่อนข้างเย็น": "orbits moderately far, quite cool climate",
"โคจรไกลสุดขั้ว อากาศหนาวมาก": "orbits extremely far, very cold environment",
# temp
"อากาศหนาวแข็ง": "freezing cold",
"เย็นสบาย": "mildly cool",
"พอเหมาะกำลังดี": "pleasantly temperate",
"ร้อนระอุ": "intensely hot",
# gravity
"โน้มถ่วงเบา (ลอยง่าย)": "light gravity (easy to float)",
"คล้ายโลก": "similar to Earth",
"หนักกว่าปกติ": "heavier than usual",
"หนักมาก": "very heavy",
# tilt
"แทบไม่เอียง": "almost no axial tilt",
"เอียงเล็กน้อย มีฤดูกาลเบาๆ": "slightly tilted, gentle seasons",
"เอียงปานกลาง ฤดูกาลเปลี่ยนแปลง": "moderately tilted, noticeable seasonal changes",
"เอียงมาก ฤดูกาลสุดขั้ว": "highly tilted, extreme seasonal shifts",
# moons
"ไม่มีดวงจันทร์": "no moons",
"มีดวงจันทร์หนึ่งดวง": "one moon",
"มีดวงจันทร์สองดวง": "two moons",
"มีดวงจันทร์สามดวง": "three moons",
"มีดวงจันทร์สี่ดวง": "four moons",
"มีดวงจันทร์ห้าวง": "five moons",
"มีดวงจันทร์หกดวง": "six moons",
"มีดวงจันทร์เจ็ดดวง": "seven moons",
"มีดวงจันทร์แปดดวง": "eight moons",
"มีดวงจันทร์เก้าดวง": "nine moons",
"มีดวงจันทร์สิบดวง": "ten moons",
}
def describe_distance_th_to_en(desc_th):
return pretranslated_map.get(desc_th, "unknown distance environment")
def describe_temp_th_to_en(desc_th):
return pretranslated_map.get(desc_th, "unknown temperature")
def describe_gravity_th_to_en(desc_th):
return pretranslated_map.get(desc_th, "unknown gravity")
def describe_tilt_th_to_en(desc_th):
return pretranslated_map.get(desc_th, "unknown axial tilt")
def describe_moons_th_to_en(desc_th):
return pretranslated_map.get(desc_th, "unknown moons")
def describe_oxygen_th_to_en(desc_th):
if desc_th == "ไม่มีออกซิเจน":
return "no oxygen"
elif desc_th == "ออกซิเจนน้อย":
return "low oxygen"
elif desc_th == "ออกซิเจนพอเหมาะ":
return "moderate oxygen"
else:
return "high oxygen"
###################################
# ฟังก์ชันบรรยาย (ภาษาไทย)
###################################
def describe_distance(distance_au):
if distance_au < 0.5:
return "โคจรใกล้ดาวฤกษ์มาก"
elif distance_au < 1.2:
return "โคจรคล้ายโลกหรืออุ่นกว่านิดหน่อย"
elif distance_au < 2.5:
return "โคจรห่างพอประมาณ อากาศค่อนข้างเย็น"
else:
return "โคจรไกลสุดขั้ว อากาศหนาวมาก"
def describe_temp(temp_c):
if temp_c < -30:
return "อากาศหนาวแข็ง"
elif temp_c < 10:
return "เย็นสบาย"
elif temp_c < 35:
return "พอเหมาะกำลังดี"
else:
return "ร้อนระอุ"
def describe_gravity(g):
if g < 0.5:
return "โน้มถ่วงเบา (ลอยง่าย)"
elif g < 1.2:
return "คล้ายโลก"
elif g < 2.0:
return "หนักกว่าปกติ"
else:
return "หนักมาก"
def describe_tilt(tilt_deg):
if tilt_deg < 5:
return "แทบไม่เอียง"
elif tilt_deg < 25:
return "เอียงเล็กน้อย มีฤดูกาลเบาๆ"
elif tilt_deg < 45:
return "เอียงปานกลาง ฤดูกาลเปลี่ยนแปลง"
else:
return "เอียงมาก ฤดูกาลสุดขั้ว"
def describe_moons(n):
if n <= 0:
return "ไม่มีดวงจันทร์"
elif n == 1:
return "มีดวงจันทร์หนึ่งดวง"
elif n == 2:
return "มีดวงจันทร์สองดวง"
elif n == 3:
return "มีดวงจันทร์สามดวง"
elif n == 4:
return "มีดวงจันทร์สี่ดวง"
elif n == 5:
return "มีดวงจันทร์ห้าวง"
elif n == 6:
return "มีดวงจันทร์หกดวง"
elif n == 7:
return "มีดวงจันทร์เจ็ดดวง"
elif n == 8:
return "มีดวงจันทร์แปดดวง"
elif n == 9:
return "มีดวงจันทร์เก้าดวง"
elif n == 10:
return "มีดวงจันทร์สิบดวง"
else:
return f"มีดวงจันทร์ {n} ดวง"
def describe_oxygen(oxygen_percent):
if oxygen_percent < 1:
return "ไม่มีออกซิเจน"
elif oxygen_percent < 10:
return "ออกซิเจนน้อย"
elif oxygen_percent < 25:
return "ออกซิเจนพอเหมาะ"
else:
return "ออกซิเจนสูง"
###################################
# 5) สร้าง Prompt 3 แบบ (Pre-translate dictionary)
###################################
def build_prompts_en(
planet_name_en, star_type_en,
dist_desc_en, temp_desc_en, grav_desc_en, tilt_desc_en, moon_desc_en, oxygen_desc_en, life_en
):
# Prompt 1
prompt1 = (
f"A vibrant space painting of planet '{planet_name_en}' orbiting a {star_type_en} star. "
f"It is {dist_desc_en}, with {temp_desc_en} conditions and {grav_desc_en} gravity. "
f"{tilt_desc_en}, {moon_desc_en}, atmosphere has {oxygen_desc_en}. Cinematic details."
)
# Prompt 2
prompt2 = (
f"On planet '{planet_name_en}', we discover {life_en} thriving in {temp_desc_en} weather, "
f"{grav_desc_en} pull, and {oxygen_desc_en} in the air. Surreal alien ecosystem, rich concept art."
)
# Prompt 3
prompt3 = (
f"Exploring the surface of '{planet_name_en}': {temp_desc_en} climate, {grav_desc_en}, "
f"{tilt_desc_en} tilt, and {moon_desc_en}. Epic environment design, atmospheric perspective."
)
return prompt1, prompt2, prompt3
###################################
# 6) ฟังก์ชันหลัก generate_planet_info
###################################
def generate_planet_info(
planet_name_th,
star_type_en,
distance_str,
diameter_str,
tilt_value,
moon_value,
oxygen_percent,
planet_type_th,
life_th
):
# parse
try:
distance_au = float(distance_str)
except:
distance_au = 1.0
try:
diameter_factor = float(diameter_str)
except:
diameter_factor = 1.0
try:
tilt_deg = float(tilt_value)
except:
tilt_deg = 23.5
try:
moon_count = int(moon_value)
except:
moon_count = 1
try:
oxygen_percent = float(oxygen_percent)
except:
oxygen_percent = 21.0
# คำนวณ
temp_c = approximate_temp_with_star(star_type_en, distance_au)
g_approx = approximate_gravity(diameter_factor, planet_type_th)
# -----------------------------
# (A) สรุปสำหรับเด็ก (ไทย)
# -----------------------------
child_summary = ""
child_summary += f"สวัสดีหนูน้อยนักสำรวจ!\n\n"
child_summary += f"**{planet_name_th}** ที่หนูสร้าง เป็น{planet_type_th} "
child_summary += f"โคจรรอบดาวฤกษ์ **{star_type_en}**\n"
child_summary += f"ระยะห่าง ~{distance_au} AU => ประเมินอุณหภูมิ ~{temp_c}°C\n"
if temp_c < -20:
child_summary += "หนาวจัดเลยทีเดียว!\n"
elif temp_c > 35:
child_summary += "ร้อนระอุทีเดียว!\n"
else:
child_summary += "อากาศดูพอเหมาะนะ!\n"
child_summary += f"แรงโน้มถ่วง ~{g_approx}g"
if g_approx < 0.5:
child_summary += " (ลอยง่าย!)"
elif g_approx > 1.5:
child_summary += " (หนักเดินลำบาก!)"
child_summary += "\n"
child_summary += f"แกนเอียง {tilt_deg}° => "
if tilt_deg > 45:
child_summary += "ฤดูกาลแปรปรวนสุดขั้ว!\n"
elif tilt_deg < 5:
child_summary += "แทบไม่เปลี่ยนฤดูกาล\n"
else:
child_summary += "มีฤดูกาลตามองศาเอียง\n"
child_summary += f"{describe_moons(moon_count)}\n"
if moon_count > 2:
child_summary += "น้ำขึ้นน้ำลงคงอลังการมาก\n"
child_summary += f"ออกซิเจน ~{oxygen_percent}%\n"
if oxygen_percent < 5:
child_summary += "น้อยมาก ต้องใช้ชุดอวกาศเลย\n"
elif oxygen_percent > 30:
child_summary += "สูงมาก ระวังไฟติดง่ายนะ\n"
child_summary += f"สิ่งมีชีวิต: **{life_th}**\n"
child_summary += "ลองคิดดูสิว่าหน้าตาจะเป็นยังไง!\n\n"
child_summary += "พร้อมลุยกันแล้วหรือยัง!?"
# -----------------------------
# (B) รายละเอียด (ไทย)
# -----------------------------
detail_th = (
f"ชื่อดาวเคราะห์(ไทย): {planet_name_th}\n"
f"ประเภทดาวฤกษ์: {star_type_en}\n"
f"ระยะ (AU): {distance_au}\n"
f"ขนาด (เท่าโลก): {diameter_factor}\n"
f"แกนเอียง: {tilt_deg}°\n"
f"จำนวนดวงจันทร์: {moon_count}\n"
f"เปอร์เซ็นต์ O2: {oxygen_percent}%\n"
f"ชนิดดาวเคราะห์(ไทย): {planet_type_th}\n"
f"สิ่งมีชีวิต(ไทย): {life_th}\n"
f"อุณหภูมิ: ~{temp_c} °C\n"
f"แรงโน้มถ่วง (สมมุติ): ~{g_approx} g\n"
)
# -----------------------------
# (C) สร้าง Prompt อังกฤษ 3 แบบ
# -----------------------------
# 1) แปลชื่อดาว, สิ่งมีชีวิต (เฉพาะ user input) ผ่านแคช
planet_name_en = translate_th_to_en(planet_name_th)
life_en = translate_th_to_en(life_th)
# 2) อธิบาย distance/temp/gravity/tilt/moon/oxygen เป็นภาษาไทย -> แปลเป็น EN จาก Dictionary
dist_desc_th = describe_distance(distance_au)
temp_desc_th = describe_temp(temp_c)
grav_desc_th = describe_gravity(g_approx)
tilt_desc_th = describe_tilt(tilt_deg)
moon_desc_th = describe_moons(moon_count)
o2_desc_th = describe_oxygen(oxygen_percent)
# แปลงผ่าน Dictionary
dist_desc_en = describe_distance_th_to_en(dist_desc_th)
temp_desc_en = describe_temp_th_to_en(temp_desc_th)
grav_desc_en = describe_gravity_th_to_en(grav_desc_th)
tilt_desc_en = describe_tilt_th_to_en(tilt_desc_th)
moon_desc_en = describe_moons_th_to_en(moon_desc_th)
oxygen_desc_en = describe_oxygen_th_to_en(o2_desc_th)
# 3) สร้าง Prompt
prompt1, prompt2, prompt3 = build_prompts_en(
planet_name_en,
star_type_en,
dist_desc_en,
temp_desc_en,
grav_desc_en,
tilt_desc_en,
moon_desc_en,
oxygen_desc_en,
life_en
)
return child_summary, detail_th, prompt1, prompt2, prompt3
###################################
# สูตรโชว์
###################################
formula_text = r"""
**สูตรอุณหภูมิ (Stefan-Boltzmann) แบบง่าย**
\\[
T = \left(\frac{(1 - A) \times L}{16 \pi \sigma \, d^2}\right)^{\frac{1}{4}} - 273.15 + 15^\circ\text{C (Greenhouse)}
\\]
- \\(A\\) = Albedo
- \\(L\\) = ความสว่างของดาว (W)
- \\(\sigma\\) = 5.67\\times10^{-8}
- \\(d\\) = ระยะทาง (m)
**สูตรแรงโน้มถ่วงนิวตัน**
\\(g = \frac{G M}{R^2}\\)
(เราใช้สมมุติว่า \\(M \propto R^3\\) => \\(g \propto R\\))
"""
###################################
# สร้าง UI ด้วย Gradio
###################################
css_code = """
body {
background-color: #F9FBFF !important;
font-family: "Kanit", sans-serif;
}
#title {
color: #4A90E2 !important;
text-align: center;
font-size: 2rem;
margin-top: 20px;
margin-bottom: 10px;
font-weight: bold;
}
.game-desc {
margin: 0 auto;
width: 90%;
background-color: #ECF6FF !important;
border: 2px dashed #B3DAFF !important;
border-radius: 10px;
padding: 15px;
color: #333333 !important;
margin-bottom: 20px;
}
/* เพิ่มกฎสำหรับแท็ก <p> ภายใน .game-desc */
.game-desc p {
color: #000000 !important; /* เปลี่ยนเป็นสีดำ */
}
/* กำหนดสีสำหรับข้อความภายใน <strong>, <em>, และ <span> ภายใน .game-desc */
.game-desc strong, .game-desc em, .game-desc span {
color: #000000 !important; /* เปลี่ยนเป็นสีดำ */
}
.btn-main {
background-color: #FFE066 !important;
border: 2px solid #FFCA28 !important;
font-weight: bold;
font-size: 1.1rem;
padding: 10px 30px;
border-radius: 10px;
margin-right: 10px;
color: #000000 !important; /* เพิ่มสีข้อความให้เข้ม */
}
#child-summary, #detail-th, #prompt1-en, #prompt2-en, #prompt3-en, #formula-box {
background-color: #FFFDF5 !important;
border: 2px solid #FFE082 !important;
border-radius: 10px;
padding: 10px;
margin-bottom: 20px;
color: #333333 !important; /* เพิ่มสีข้อความให้เข้ม */
}
.copy-btn {
background-color: #F06292 !important;
border: 2px solid #E91E63 !important;
font-weight: bold;
font-size: 0.8rem;
padding: 5px 10px;
border-radius: 5px;
margin-top: 5px;
color: #FFFFFF !important; /* สีข้อความของปุ่มคัดลอก */
}
/* เพิ่มกฎสำหรับข้อความภายใน .game-desc ที่อาจถูกตั้งค่าโดยธีม */
.game-desc * {
color: #000000 !important; /* เปลี่ยนเป็นสีดำ */
}
"""
def show_formula(state):
new_state = not state
return new_state, gr.update(visible=new_state)
def welcome_text():
return "ยินดีต้อนรับสู่ **ZenityX Planetary Adventure**! ลองกรอกข้อมูลแล้วกด 'สร้างโลกแฟนตาซี' สิ!"
with gr.Blocks(css=css_code) as demo:
gr.Markdown("<h1 id='title'>ZenityX Planetary Adventure</h1>")
gr.Markdown("""
<div class="game-desc">
<p>พัฒนาโดย <strong>สถาบัน ZenityX AI Studio</strong></p>
<p>สร้างโลกแฟนตาซีของตัวเองด้วยการกรอกข้อมูลด้านล่าง! แล้วมาสนุกกับการสำรวจโลกใหม่กันเถอะ!</p>
<p>ใส่ข้อมูล แล้วกด <strong>"สร้างโลกแฟนตาซี"</strong> เพื่อดู
<em>สรุปสำหรับเด็ก</em>, <em>รายละเอียด (ไทย)</em>, และ <em>3 Prompts อังกฤษ</em></p>
<p>ถ้าต้องการดูสูตรคำนวณ กดปุ่ม <strong>"โชว์สูตรคำนวณ"</strong> ได้เลย!</p>
</div>
""")
formula_state = gr.State(value=False)
formula_md = gr.Markdown(formula_text, visible=False, elem_id="formula-box")
show_formula_btn = gr.Button("โชว์สูตรคำนวณ")
show_formula_btn.click(fn=show_formula, inputs=formula_state, outputs=[formula_state, formula_md])
with gr.Row():
with gr.Column():
planet_name_th = gr.Textbox(label="ชื่อดาวเคราะห์ (ไทย)", placeholder="เช่น ดาวซานาดา, ดาวโซลาริส...")
star_type_en = gr.Dropdown(
label="ประเภทดาวฤกษ์",
choices=["Red Dwarf", "White Dwarf", "Sun-like", "Blue Giant", "Supergiant"],
value="Sun-like"
)
distance_au = gr.Textbox(label="ระยะห่าง (AU)", placeholder="เช่น 1, 0.5, 2...")
diameter_factor = gr.Textbox(label="ขนาด (เท่าโลก)", placeholder="เช่น 1, 2, 0.5...")
planet_type_th = gr.Dropdown(
label="ชนิดดาวเคราะห์ (ไทย)",
choices=["ดาวหิน", "ดาวก๊าซ", "ดาวน้ำแข็ง"],
value="ดาวหิน"
)
with gr.Column():
tilt_slider = gr.Slider(0, 90, step=1, value=23.5, label="แกนเอียง (องศา)")
moon_slider = gr.Slider(0, 10, step=1, value=1, label="จำนวนดวงจันทร์")
oxygen_slider = gr.Slider(0, 100, step=1, value=21, label="% ออกซิเจน")
life_th = gr.Textbox(label="สิ่งมีชีวิต (ไทย)", placeholder="เช่น แมลงยักษ์เรืองแสง...")
create_btn = gr.Button("สร้างโลกแฟนตาซี", elem_classes="btn-main")
child_summary_out = gr.Textbox(label="สรุปสำหรับเด็ก (ไทย)", interactive=False, elem_id="child-summary")
detail_th_out = gr.Textbox(label="รายละเอียด (ไทย)", interactive=False, elem_id="detail-th")
prompt1_en_out = gr.Textbox(label="Prompt #1 (English)", interactive=False, elem_id="prompt1-en")
prompt2_en_out = gr.Textbox(label="Prompt #2 (English)", interactive=False, elem_id="prompt2-en")
prompt3_en_out = gr.Textbox(label="Prompt #3 (English)", interactive=False, elem_id="prompt3-en")
# ฟังก์ชันคัดลอก Prompt
copy_button_html = """
<div style="display: flex; align-items: center; gap: 10px;">
<button class="copy-btn" onclick="copyText('1')">คัดลอก Prompt #1</button>
<button class="copy-btn" onclick="copyText('2')">คัดลอก Prompt #2</button>
<button class="copy-btn" onclick="copyText('3')">คัดลอก Prompt #3</button>
</div>
<script>
function copyText(promptNumber) {
const promptBox = document.querySelector(`#prompt${promptNumber}-en textarea`);
if (!promptBox) {
alert('ไม่พบข้อความ Prompt!');
return;
}
const promptText = promptBox.value;
navigator.clipboard.writeText(promptText);
alert('คัดลอก Prompt แล้ว! นำไปใช้กับ AI สร้างภาพได้เลย~');
}
</script>
"""
gr.HTML(copy_button_html)
def generate_wrapper(
p_name_th, s_type_en, dist_au, dia_fac, tilt_val, moon_val, oxy_val, p_type_th, l_th
):
return generate_planet_info(
planet_name_th=p_name_th,
star_type_en=s_type_en,
distance_str=dist_au,
diameter_str=dia_fac,
tilt_value=str(tilt_val),
moon_value=str(moon_val),
oxygen_percent=oxy_val,
planet_type_th=p_type_th,
life_th=l_th
)
create_btn.click(
fn=generate_wrapper,
inputs=[
planet_name_th,
star_type_en,
distance_au,
diameter_factor,
tilt_slider,
moon_slider,
oxygen_slider,
planet_type_th,
life_th
],
outputs=[
child_summary_out,
detail_th_out,
prompt1_en_out,
prompt2_en_out,
prompt3_en_out
]
)
demo.load(fn=welcome_text, inputs=None, outputs=child_summary_out)
demo.launch()