Update app.py
Browse files
app.py
CHANGED
|
@@ -241,8 +241,11 @@ def dna_embed(seq: str, model_name: str = "zhihan1996/DNABERT-2-117M") -> Dict:
|
|
| 241 |
except ImportError:
|
| 242 |
return {"error": "einops package required. Installing..."}
|
| 243 |
|
| 244 |
-
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
| 246 |
model.eval()
|
| 247 |
|
| 248 |
with torch.no_grad():
|
|
@@ -289,18 +292,34 @@ def build_context(query: str, docs: List[str], index, model, use_web: bool, web_
|
|
| 289 |
def answer_question(query: str, context: str) -> str:
|
| 290 |
"""Generate answer"""
|
| 291 |
system = (
|
| 292 |
-
"You are
|
| 293 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
)
|
| 295 |
|
| 296 |
-
user_msg = f"Context:\n{context}\n\
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
|
| 298 |
messages = [
|
| 299 |
{"role": "system", "content": system},
|
| 300 |
{"role": "user", "content": user_msg}
|
| 301 |
]
|
| 302 |
|
| 303 |
-
return call_llm(messages, temperature=0.4, max_tokens=
|
| 304 |
|
| 305 |
# --------------- Streamlit UI ---------------
|
| 306 |
|
|
@@ -423,73 +442,191 @@ with tab1:
|
|
| 423 |
with tab2:
|
| 424 |
st.subheader("๐งฌ Protein Analysis")
|
| 425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
protein_seq = st.text_area(
|
| 427 |
-
"
|
| 428 |
value="MKTIIALSYIFCLVFA",
|
|
|
|
| 429 |
height=100
|
| 430 |
)
|
| 431 |
|
| 432 |
-
|
| 433 |
-
|
| 434 |
with col1:
|
| 435 |
-
if st.button("
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
# Basic stats
|
| 439 |
-
st.write(f"**Length:** {len(seq)}")
|
| 440 |
-
st.write(f"**Unique AAs:** {len(set(seq))}")
|
| 441 |
-
|
| 442 |
-
# ESM-2 embedding
|
| 443 |
-
if TORCH_AVAILABLE and TRANSFORMERS_AVAILABLE:
|
| 444 |
-
with st.spinner("Computing embedding..."):
|
| 445 |
-
result = esm2_embed(seq, esm_model)
|
| 446 |
-
if "error" in result:
|
| 447 |
-
st.error(result["error"])
|
| 448 |
-
else:
|
| 449 |
-
st.success(f"Embedding size: {result['size']}")
|
| 450 |
-
st.json({"preview": result["embedding"][:5]})
|
| 451 |
-
else:
|
| 452 |
-
st.warning("PyTorch not available for embeddings")
|
| 453 |
-
|
| 454 |
with col2:
|
| 455 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
|
| 457 |
# DNA tab
|
| 458 |
with tab3:
|
| 459 |
st.subheader("๐งฌ DNA Analysis")
|
| 460 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
dna_seq = st.text_area(
|
| 462 |
-
"
|
| 463 |
value="ATGCGATCGTAGC",
|
|
|
|
| 464 |
height=100
|
| 465 |
)
|
| 466 |
|
| 467 |
-
|
| 468 |
-
|
| 469 |
with col1:
|
| 470 |
-
if st.button("
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
|
| 473 |
-
|
| 474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
|
| 476 |
-
|
| 477 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
|
| 479 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
if TORCH_AVAILABLE and TRANSFORMERS_AVAILABLE:
|
| 481 |
-
|
|
|
|
| 482 |
result = dna_embed(seq, dna_model)
|
| 483 |
if "error" in result:
|
| 484 |
st.error(result["error"])
|
| 485 |
else:
|
| 486 |
-
st.success(
|
| 487 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
else:
|
| 489 |
-
st.warning("
|
| 490 |
-
|
| 491 |
-
with col2:
|
| 492 |
-
st.info("Motif analysis and structure prediction coming soon")
|
| 493 |
|
| 494 |
# About tab
|
| 495 |
with tab4:
|
|
|
|
| 241 |
except ImportError:
|
| 242 |
return {"error": "einops package required. Installing..."}
|
| 243 |
|
| 244 |
+
# DNABERT-2 ํน๋ณ ์ฒ๋ฆฌ
|
| 245 |
+
from transformers import BertModel, BertTokenizer
|
| 246 |
+
|
| 247 |
+
tokenizer = BertTokenizer.from_pretrained(model_name, trust_remote_code=True)
|
| 248 |
+
model = BertModel.from_pretrained(model_name, trust_remote_code=True)
|
| 249 |
model.eval()
|
| 250 |
|
| 251 |
with torch.no_grad():
|
|
|
|
| 292 |
def answer_question(query: str, context: str) -> str:
|
| 293 |
"""Generate answer"""
|
| 294 |
system = (
|
| 295 |
+
"You are an expert bioinformatics assistant who explains complex biological concepts in an accessible way. "
|
| 296 |
+
"Your responses should be:\n"
|
| 297 |
+
"1. Comprehensive yet easy to understand\n"
|
| 298 |
+
"2. Well-structured with clear sections\n"
|
| 299 |
+
"3. Include relevant examples and analogies\n"
|
| 300 |
+
"4. Provide actionable insights when appropriate\n"
|
| 301 |
+
"5. Use Korean if the user writes in Korean, otherwise English\n"
|
| 302 |
+
"6. Never provide medical diagnosis or treatment advice\n"
|
| 303 |
+
"7. Format your response with headers, bullet points, and clear paragraphs\n"
|
| 304 |
+
"8. Aim for 300-500 words minimum for complex questions"
|
| 305 |
)
|
| 306 |
|
| 307 |
+
user_msg = f"""Context information:\n{context}\n\n
|
| 308 |
+
User Question: {query}
|
| 309 |
+
|
| 310 |
+
Please provide a detailed, well-structured response that:
|
| 311 |
+
- Directly answers the question
|
| 312 |
+
- Explains the biological background
|
| 313 |
+
- Includes practical implications when relevant
|
| 314 |
+
- Uses simple analogies to explain complex concepts
|
| 315 |
+
- Cites the context when appropriate"""
|
| 316 |
|
| 317 |
messages = [
|
| 318 |
{"role": "system", "content": system},
|
| 319 |
{"role": "user", "content": user_msg}
|
| 320 |
]
|
| 321 |
|
| 322 |
+
return call_llm(messages, temperature=0.4, max_tokens=1500)
|
| 323 |
|
| 324 |
# --------------- Streamlit UI ---------------
|
| 325 |
|
|
|
|
| 442 |
with tab2:
|
| 443 |
st.subheader("๐งฌ Protein Analysis")
|
| 444 |
|
| 445 |
+
st.info("""
|
| 446 |
+
**๋จ๋ฐฑ์ง ์์ด ๋ถ์์ด๋?**
|
| 447 |
+
- ๋จ๋ฐฑ์ง์ ์๋ฏธ๋
ธ์ฐ ์์ด์ AI๊ฐ ๋ถ์ํ์ฌ ๊ธฐ๋ฅ๊ณผ ๊ตฌ์กฐ๋ฅผ ์์ธกํฉ๋๋ค
|
| 448 |
+
- ESM-2๋ Meta๊ฐ ๊ฐ๋ฐํ AI๋ก, 6์ต 5์ฒ๋ง๊ฐ ๋จ๋ฐฑ์ง์ ํ์ตํ์ต๋๋ค
|
| 449 |
+
- ์ฉ๋: ์ ์ฝ ๊ฐ๋ฐ, ์ง๋ณ ์ฐ๊ตฌ, ์งํ ๋ถ์ ๋ฑ
|
| 450 |
+
""")
|
| 451 |
+
|
| 452 |
protein_seq = st.text_area(
|
| 453 |
+
"๋จ๋ฐฑ์ง ์์ด ์
๋ ฅ (๋ณต์ฌ-๋ถ์ฌ๋ฃ๊ธฐ ๊ฐ๋ฅ):",
|
| 454 |
value="MKTIIALSYIFCLVFA",
|
| 455 |
+
help="๋จ๋ฐฑ์ง ์์ด์ 20๊ฐ ์๋ฏธ๋
ธ์ฐ ๋ฌธ์(A,C,D,E,F,G,H,I,K,L,M,N,P,Q,R,S,T,V,W,Y)๋ก ๊ตฌ์ฑ๋ฉ๋๋ค",
|
| 456 |
height=100
|
| 457 |
)
|
| 458 |
|
| 459 |
+
st.markdown("**์์ ์์ด (ํด๋ฆญํด์ ๋ณต์ฌ):**")
|
| 460 |
+
col1, col2, col3 = st.columns(3)
|
| 461 |
with col1:
|
| 462 |
+
if st.button("์ธ์๋ฆฐ", key="ins"):
|
| 463 |
+
st.code("FVNQHLCGSHLVEALYLVCGERGFFYTPKT", language=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
with col2:
|
| 465 |
+
if st.button("์๋ํ", key="end"):
|
| 466 |
+
st.code("YGGFMTSEKSQTPLVTLFKNAIIKNAYKKGE", language=None)
|
| 467 |
+
with col3:
|
| 468 |
+
if st.button("์ฅ์ํ ์ ", key="oxy"):
|
| 469 |
+
st.code("CYIQNCPLG", language=None)
|
| 470 |
+
|
| 471 |
+
if st.button("๐ฌ ๋จ๋ฐฑ์ง ๋ถ์ ์์", type="primary"):
|
| 472 |
+
seq = protein_seq.strip().upper()
|
| 473 |
+
|
| 474 |
+
# Basic stats
|
| 475 |
+
st.markdown("### ๐ ๊ธฐ๋ณธ ๋ถ์ ๊ฒฐ๊ณผ")
|
| 476 |
+
col1, col2 = st.columns(2)
|
| 477 |
+
|
| 478 |
+
with col1:
|
| 479 |
+
st.metric("์์ด ๊ธธ์ด", f"{len(seq)} ์๋ฏธ๋
ธ์ฐ")
|
| 480 |
+
st.metric("๋ถ์๋ (์ถ์ )", f"~{len(seq) * 110} Da")
|
| 481 |
+
|
| 482 |
+
with col2:
|
| 483 |
+
unique_aa = len(set(seq))
|
| 484 |
+
st.metric("์ฌ์ฉ๋ ์๋ฏธ๋
ธ์ฐ ์ข
๋ฅ", f"{unique_aa}๊ฐ")
|
| 485 |
+
hydrophobic = sum(1 for aa in seq if aa in "AVILMFYW")
|
| 486 |
+
st.metric("์์์ฑ ๋น์จ", f"{hydrophobic/len(seq)*100:.1f}%")
|
| 487 |
+
|
| 488 |
+
# AI Analysis
|
| 489 |
+
if TORCH_AVAILABLE and TRANSFORMERS_AVAILABLE:
|
| 490 |
+
st.markdown("### ๐ค AI ์๋ฒ ๋ฉ ๋ถ์")
|
| 491 |
+
with st.spinner("AI ๋ชจ๋ธ์ด ๋จ๋ฐฑ์ง์ ๋ถ์์ค... (10-30์ด)"):
|
| 492 |
+
result = esm2_embed(seq, esm_model)
|
| 493 |
+
if "error" in result:
|
| 494 |
+
st.error(result["error"])
|
| 495 |
+
else:
|
| 496 |
+
st.success("โ
AI ๋ถ์ ์๋ฃ!")
|
| 497 |
+
|
| 498 |
+
col1, col2 = st.columns(2)
|
| 499 |
+
with col1:
|
| 500 |
+
st.metric("๋ฒกํฐ ์ฐจ์", result['size'])
|
| 501 |
+
st.caption("์ด ์ซ์๋ค์ ๋จ๋ฐฑ์ง์ ํน์ฑ์ ์์นํํ ๊ฒ์
๋๋ค")
|
| 502 |
+
|
| 503 |
+
with col2:
|
| 504 |
+
st.markdown("**์๋ฒ ๋ฉ ๋ฒกํฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ:**")
|
| 505 |
+
st.code(result["embedding"][:5])
|
| 506 |
+
|
| 507 |
+
st.markdown("""
|
| 508 |
+
**๐ฏ ์ด ๋ถ์์ ํ์ฉ:**
|
| 509 |
+
- ์ ์ฌํ ๊ธฐ๋ฅ์ ๋จ๋ฐฑ์ง ์ฐพ๊ธฐ
|
| 510 |
+
- ๊ตฌ์กฐ ์์ธก์ ๊ธฐ์ด ๋ฐ์ดํฐ
|
| 511 |
+
- ๋์ฐ๋ณ์ด ์ํฅ ์์ธก
|
| 512 |
+
- ์ ์ฝ ํ๊ฒ ๋ฐ๊ตด
|
| 513 |
+
""")
|
| 514 |
+
else:
|
| 515 |
+
st.warning("โ ๏ธ AI ๋ชจ๋ธ ๋ก๋ฉ ์ค... ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์")
|
| 516 |
|
| 517 |
# DNA tab
|
| 518 |
with tab3:
|
| 519 |
st.subheader("๐งฌ DNA Analysis")
|
| 520 |
|
| 521 |
+
st.info("""
|
| 522 |
+
**DNA ์์ด ๋ถ์์ด๋?**
|
| 523 |
+
- DNA์ ์ผ๊ธฐ์์ด(A,T,G,C)์ AI๊ฐ ๋ถ์ํ์ฌ ๊ธฐ๋ฅ์ ์์ธกํฉ๋๋ค
|
| 524 |
+
- DNABERT-2๋ ์ธ๊ฐ ๊ฒ๋ ์ ์ฒด๋ฅผ ํ์ตํ AI ๋ชจ๋ธ์
๋๋ค
|
| 525 |
+
- ์ฉ๋: ์ ์ ์ ๊ธฐ๋ฅ ์์ธก, ์ง๋ณ ๋ณ์ด ๋ฐ๊ฒฌ, ์งํ ์ฐ๊ตฌ ๋ฑ
|
| 526 |
+
""")
|
| 527 |
+
|
| 528 |
dna_seq = st.text_area(
|
| 529 |
+
"DNA ์์ด ์
๋ ฅ (๋ณต์ฌ-๋ถ์ฌ๋ฃ๊ธฐ ๊ฐ๋ฅ):",
|
| 530 |
value="ATGCGATCGTAGC",
|
| 531 |
+
help="DNA๋ 4๊ฐ ์ผ๊ธฐ(A: ์๋ฐ๋, T: ํฐ๋ฏผ, G: ๊ตฌ์๋, C: ์ํ ์ )๋ก ๊ตฌ์ฑ๋ฉ๋๋ค",
|
| 532 |
height=100
|
| 533 |
)
|
| 534 |
|
| 535 |
+
st.markdown("**์์ ์์ด (ํด๋ฆญํด์ ๋ณต์ฌ):**")
|
| 536 |
+
col1, col2, col3 = st.columns(3)
|
| 537 |
with col1:
|
| 538 |
+
if st.button("TATA ๋ฐ์ค", key="tata"):
|
| 539 |
+
st.code("GCGCGATATAAAGGCGCGGGCGCGCG", language=None)
|
| 540 |
+
st.caption("์ ์ ์ ๋ฐํ ์์ ์ ํธ")
|
| 541 |
+
with col2:
|
| 542 |
+
if st.button("ํ๋ก๋ชจํฐ", key="prom"):
|
| 543 |
+
st.code("TTGACAGGCTAGCTCAGTCCTAGGTATAATGCTAGC", language=None)
|
| 544 |
+
st.caption("์ ์ ์ ์กฐ์ ์์ญ")
|
| 545 |
+
with col3:
|
| 546 |
+
if st.button("CRISPR ํ๊ฒ", key="crispr"):
|
| 547 |
+
st.code("GTCACCTCCAATGACTAGGGTGG", language=None)
|
| 548 |
+
st.caption("์ ์ ์ ํธ์ง ๋ถ์")
|
| 549 |
+
|
| 550 |
+
if st.button("๐ฌ DNA ๋ถ์ ์์", type="primary"):
|
| 551 |
+
seq = dna_seq.strip().upper().replace("U", "T") # RNA์ U๋ฅผ T๋ก ๋ณํ
|
| 552 |
+
seq = ''.join(c for c in seq if c in 'ATGC') # ATGC๋ง ๋จ๊ธฐ๊ธฐ
|
| 553 |
+
|
| 554 |
+
if len(seq) < 3:
|
| 555 |
+
st.error("์ต์ 3๊ฐ ์ด์์ ์ผ๊ธฐ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์")
|
| 556 |
+
else:
|
| 557 |
+
st.markdown("### ๐ ๊ธฐ๋ณธ ๋ถ์ ๊ฒฐ๊ณผ")
|
| 558 |
+
col1, col2 = st.columns(2)
|
| 559 |
|
| 560 |
+
with col1:
|
| 561 |
+
st.metric("์์ด ๊ธธ์ด", f"{len(seq)} bp")
|
| 562 |
+
gc = (seq.count("G") + seq.count("C")) / len(seq) * 100
|
| 563 |
+
st.metric("GC ํจ๋", f"{gc:.1f}%")
|
| 564 |
+
if gc > 60:
|
| 565 |
+
st.caption("๐ด ๋์: ์์ ์ ์ด์ง๋ง ๋ณต์ ์ด๋ ค์")
|
| 566 |
+
elif gc < 40:
|
| 567 |
+
st.caption("๐ต ๋ฎ์: ๋ถ์์ ํ์ง๋ง ๋ณต์ ์ฉ์ด")
|
| 568 |
+
else:
|
| 569 |
+
st.caption("๐ข ์ ์ : ์ผ๋ฐ์ ์ธ ๋ฒ์")
|
| 570 |
|
| 571 |
+
with col2:
|
| 572 |
+
at = (seq.count("A") + seq.count("T")) / len(seq) * 100
|
| 573 |
+
st.metric("AT ํจ๋", f"{at:.1f}%")
|
| 574 |
+
|
| 575 |
+
# ์ฝ๋ ๋ถ์ (3์ ๋ฐฐ์์ธ ๊ฒฝ์ฐ)
|
| 576 |
+
if len(seq) % 3 == 0:
|
| 577 |
+
st.metric("๊ฐ๋ฅํ ์ฝ๋ ์", f"{len(seq)//3}๊ฐ")
|
| 578 |
+
st.caption("๋จ๋ฐฑ์ง๋ก ๋ฒ์ญ ๊ฐ๋ฅ")
|
| 579 |
+
|
| 580 |
+
# ํน๋ณ ์์ด ์ฐพ๊ธฐ
|
| 581 |
+
st.markdown("### ๐ ์ฃผ์ ๋ชจํฐํ ๊ฒ์")
|
| 582 |
+
motifs_found = []
|
| 583 |
|
| 584 |
+
if "TATAAAA" in seq or "TATAAA" in seq:
|
| 585 |
+
motifs_found.append("โ
TATA box ๋ฐ๊ฒฌ (์ ์ฌ ์์ ์ ํธ)")
|
| 586 |
+
if "CAAT" in seq or "CCAAT" in seq:
|
| 587 |
+
motifs_found.append("โ
CAAT box ๋ฐ๊ฒฌ (์ ์ฌ ์กฐ์ )")
|
| 588 |
+
if "ATG" in seq:
|
| 589 |
+
motifs_found.append("โ
์์ ์ฝ๋(ATG) ๋ฐ๊ฒฌ")
|
| 590 |
+
if "TAA" in seq or "TAG" in seq or "TGA" in seq:
|
| 591 |
+
motifs_found.append("โ
์ ์ง ์ฝ๋ ๋ฐ๊ฒฌ")
|
| 592 |
+
if seq.count("CG") > len(seq)/20:
|
| 593 |
+
motifs_found.append("โ
CpG ์ฌ ๊ฐ๋ฅ์ฑ (์ ์ ์ ์กฐ์ )")
|
| 594 |
+
|
| 595 |
+
if motifs_found:
|
| 596 |
+
for motif in motifs_found:
|
| 597 |
+
st.write(motif)
|
| 598 |
+
else:
|
| 599 |
+
st.write("ํน๋ณํ ๋ชจํฐํ๊ฐ ๋ฐ๊ฒฌ๋์ง ์์์ต๋๋ค")
|
| 600 |
+
|
| 601 |
+
# AI Analysis
|
| 602 |
if TORCH_AVAILABLE and TRANSFORMERS_AVAILABLE:
|
| 603 |
+
st.markdown("### ๐ค AI ์๋ฒ ๋ฉ ๋ถ์")
|
| 604 |
+
with st.spinner("AI ๋ชจ๋ธ์ด DNA๋ฅผ ๋ถ์์ค... (10-30์ด)"):
|
| 605 |
result = dna_embed(seq, dna_model)
|
| 606 |
if "error" in result:
|
| 607 |
st.error(result["error"])
|
| 608 |
else:
|
| 609 |
+
st.success("โ
AI ๋ถ์ ์๋ฃ!")
|
| 610 |
+
|
| 611 |
+
col1, col2 = st.columns(2)
|
| 612 |
+
with col1:
|
| 613 |
+
st.metric("๋ฒกํฐ ์ฐจ์", result['size'])
|
| 614 |
+
st.caption("DNA ํน์ฑ์ ์์นํํ ๊ฒฐ๊ณผ์
๋๋ค")
|
| 615 |
+
|
| 616 |
+
with col2:
|
| 617 |
+
st.markdown("**์๋ฒ ๋ฉ ๋ฒกํฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ:**")
|
| 618 |
+
st.code(result["embedding"][:5])
|
| 619 |
+
|
| 620 |
+
st.markdown("""
|
| 621 |
+
**๐ฏ ์ด ๋ถ์์ ํ์ฉ:**
|
| 622 |
+
- ์ ์ ์ ๊ธฐ๋ฅ ์์ธก
|
| 623 |
+
- ํ๋ก๋ชจํฐ/์ธํธ์ ์ฐพ๊ธฐ
|
| 624 |
+
- ์งํ์ ๋ณด์กด ์์ญ ๋ฐ๊ฒฌ
|
| 625 |
+
- ์ง๋ณ ๊ด๋ จ ๋ณ์ด ์์ธก
|
| 626 |
+
- CRISPR ํ๊ฒ ๋ถ์ ํ๊ฐ
|
| 627 |
+
""")
|
| 628 |
else:
|
| 629 |
+
st.warning("โ ๏ธ AI ๋ชจ๋ธ ๋ก๋ฉ ์ค... ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์")
|
|
|
|
|
|
|
|
|
|
| 630 |
|
| 631 |
# About tab
|
| 632 |
with tab4:
|