openfree commited on
Commit
dee1d98
ยท
verified ยท
1 Parent(s): 5fa5891

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +173 -381
app.py CHANGED
@@ -973,9 +973,9 @@ class UnifiedAudioConverter:
973
  except Exception as e:
974
  raise RuntimeError(f"Failed to extract conversation: {e}")
975
 
976
- def parse_conversation_text(self, text: str) -> Dict:
977
  """Parse conversation text back to JSON format"""
978
- lines = text.strip().split('\n')
979
  conversation_data = {"conversation": []}
980
 
981
  for line in lines:
@@ -1327,7 +1327,7 @@ def update_tts_engine_for_korean(language):
1327
  choices=["Edge-TTS"],
1328
  value="Edge-TTS",
1329
  label="TTS Engine",
1330
- info="Korean is only supported by Edge-TTS",
1331
  interactive=False
1332
  )
1333
  else:
@@ -1363,399 +1363,192 @@ if LLAMA_CPP_AVAILABLE:
1363
  print(f"Failed to download model at startup: {e}")
1364
 
1365
 
1366
- # Custom CSS with beautiful blue gradient theme
1367
- custom_css = """
1368
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
1369
-
1370
- /* Global styles with beautiful blue gradient background */
1371
- .gradio-container {
1372
- font-family: 'Inter', sans-serif !important;
1373
- background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #6B8DD6 50%, #8E37D7 75%, #667eea 100%) !important;
1374
- background-size: 400% 400% !important;
1375
- animation: gradient 15s ease infinite !important;
1376
- min-height: 100vh !important;
1377
- }
1378
-
1379
- @keyframes gradient {
1380
- 0% { background-position: 0% 50%; }
1381
- 50% { background-position: 100% 50%; }
1382
- 100% { background-position: 0% 50%; }
1383
- }
1384
-
1385
- /* Main container styling */
1386
- .container {
1387
- max-width: 1200px !important;
1388
- margin: 0 auto !important;
1389
- padding: 20px !important;
1390
- }
1391
-
1392
- /* Header styling */
1393
- .gr-markdown h1 {
1394
- background: linear-gradient(120deg, #ffffff 0%, #e0e0e0 100%);
1395
- -webkit-background-clip: text;
1396
- -webkit-text-fill-color: transparent;
1397
- font-size: 3em !important;
1398
- font-weight: 700 !important;
1399
- text-align: center !important;
1400
- margin-bottom: 0.5em !important;
1401
- text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
1402
- }
1403
-
1404
- /* Subheader styling */
1405
- .subtitle {
1406
- text-align: center !important;
1407
- color: rgba(255, 255, 255, 0.9) !important;
1408
- font-size: 0.95em !important;
1409
- margin-bottom: 1.5em !important;
1410
- font-weight: 400 !important;
1411
- line-height: 1.6 !important;
1412
- }
1413
-
1414
- /* Card-like sections */
1415
- .gr-form, .gr-box {
1416
- background: rgba(255, 255, 255, 0.95) !important;
1417
- backdrop-filter: blur(10px) !important;
1418
- border-radius: 16px !important;
1419
- padding: 24px !important;
1420
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important;
1421
- border: 1px solid rgba(255, 255, 255, 0.2) !important;
1422
- margin-bottom: 20px !important;
1423
- }
1424
-
1425
- /* Button styling */
1426
- .gr-button {
1427
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
1428
- border: none !important;
1429
- color: white !important;
1430
- padding: 12px 24px !important;
1431
- font-weight: 600 !important;
1432
- font-size: 1.05em !important;
1433
- border-radius: 8px !important;
1434
- cursor: pointer !important;
1435
- transition: all 0.3s ease !important;
1436
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
1437
- }
1438
-
1439
- .gr-button:hover {
1440
- transform: translateY(-2px) !important;
1441
- box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
1442
- }
1443
-
1444
- .gr-button.primary {
1445
- background: linear-gradient(135deg, #8E37D7 0%, #667eea 100%) !important;
1446
- font-size: 1.1em !important;
1447
- padding: 14px 28px !important;
1448
- }
1449
-
1450
- .gr-button.secondary {
1451
- background: linear-gradient(135deg, #764ba2 0%, #6B8DD6 100%) !important;
1452
- }
1453
-
1454
- /* Input fields styling */
1455
- .gr-textbox, .gr-dropdown {
1456
- border: 2px solid #e1e4f5 !important;
1457
- border-radius: 8px !important;
1458
- font-size: 1em !important;
1459
- transition: all 0.3s ease !important;
1460
- background-color: rgba(255, 255, 255, 0.9) !important;
1461
- }
1462
-
1463
- .gr-textbox:focus, .gr-dropdown:focus {
1464
- border-color: #667eea !important;
1465
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
1466
- }
1467
-
1468
- /* Radio buttons styling */
1469
- .gr-radio {
1470
- background: rgba(255, 255, 255, 0.8) !important;
1471
- border-radius: 8px !important;
1472
- padding: 12px !important;
1473
- }
1474
-
1475
- .gr-radio label {
1476
- color: #374151 !important;
1477
- font-weight: 500 !important;
1478
- cursor: pointer !important;
1479
- transition: all 0.2s ease !important;
1480
- }
1481
-
1482
- .gr-radio input[type="radio"]:checked + label {
1483
- color: #667eea !important;
1484
- font-weight: 600 !important;
1485
- }
1486
-
1487
- /* Tab styling */
1488
- .gr-tabs {
1489
- border-radius: 12px !important;
1490
- background: rgba(255, 255, 255, 0.9) !important;
1491
- padding: 4px !important;
1492
- }
1493
-
1494
- .gr-tab {
1495
- border-radius: 8px !important;
1496
- padding: 10px 20px !important;
1497
- font-weight: 500 !important;
1498
- transition: all 0.3s ease !important;
1499
- }
1500
-
1501
- .gr-tab.selected {
1502
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
1503
- color: white !important;
1504
- }
1505
-
1506
- /* Status message styling */
1507
- .gr-markdown.status {
1508
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
1509
- color: white !important;
1510
- padding: 16px !important;
1511
- border-radius: 8px !important;
1512
- margin: 16px 0 !important;
1513
- font-weight: 500 !important;
1514
- }
1515
-
1516
- /* Textbox for conversation output */
1517
- .conversation-output {
1518
- background: rgba(249, 250, 251, 0.95) !important;
1519
- border: 2px solid #e5e7eb !important;
1520
- border-radius: 12px !important;
1521
- font-family: 'Inter', monospace !important;
1522
- font-size: 0.95em !important;
1523
- line-height: 1.8 !important;
1524
- padding: 20px !important;
1525
- }
1526
-
1527
- /* Examples section */
1528
- .gr-examples {
1529
- background: rgba(255, 255, 255, 0.8) !important;
1530
- border-radius: 12px !important;
1531
- padding: 20px !important;
1532
- margin-top: 24px !important;
1533
- }
1534
-
1535
- /* Badge container */
1536
- .badge-container {
1537
- text-align: center !important;
1538
- margin-bottom: 1em !important;
1539
- }
1540
-
1541
- /* Info boxes */
1542
- .info-box {
1543
- background: rgba(102, 126, 234, 0.1) !important;
1544
- border-left: 4px solid #667eea !important;
1545
- padding: 16px !important;
1546
- border-radius: 8px !important;
1547
- margin: 16px 0 !important;
1548
- }
1549
-
1550
- /* Loading animation */
1551
- .gr-loading {
1552
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
1553
- }
1554
-
1555
- /* Audio player styling */
1556
- .gr-audio {
1557
- border-radius: 8px !important;
1558
- background: rgba(255, 255, 255, 0.9) !important;
1559
- padding: 16px !important;
1560
- }
1561
-
1562
- /* Group styling */
1563
- .gr-group {
1564
- background: rgba(255, 255, 255, 0.05) !important;
1565
- border-radius: 12px !important;
1566
- padding: 16px !important;
1567
- margin: 12px 0 !important;
1568
- border: 1px solid rgba(255, 255, 255, 0.1) !important;
1569
- }
1570
-
1571
- /* Markdown content in groups */
1572
- .gr-group .gr-markdown {
1573
- color: #1f2937 !important;
1574
- font-size: 0.95em !important;
1575
- line-height: 1.6 !important;
1576
- }
1577
-
1578
- /* Status output specific styling */
1579
- #status_output {
1580
- background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
1581
- color: white !important;
1582
- padding: 12px 20px !important;
1583
- border-radius: 8px !important;
1584
- font-weight: 500 !important;
1585
- text-align: center !important;
1586
- }
1587
-
1588
- /* Error message styling */
1589
- .error-message {
1590
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important;
1591
- color: white !important;
1592
- padding: 12px 20px !important;
1593
- border-radius: 8px !important;
1594
- }
1595
- """
1596
-
1597
- # Gradio Interface
1598
- with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="AI Podcast Generator") as demo:
1599
- # Discord Badge
1600
- gr.HTML("""
1601
- <div class="badge-container">
1602
- <p>
1603
- <a href="https://discord.gg/openfreeai" target="_blank">
1604
- <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="badge">
1605
- </a>
1606
- </p>
1607
- </div>
1608
- """)
1609
-
1610
- gr.Markdown("# ๐ŸŽ™๏ธ AI Podcast Generator - Professional Edition")
1611
- gr.Markdown(
1612
- """<div class="subtitle">
1613
- Transform any content into engaging professional podcast conversations with deep insights and expert analysis<br>
1614
- Powered by advanced AI with real-time web search integration for comprehensive, up-to-date discussions
1615
- </div>""",
1616
- elem_classes=["subtitle"]
1617
- )
1618
-
1619
- # Status display
1620
- with gr.Row():
1621
- gr.Markdown(f"""
1622
- ### ๐Ÿค– System Configuration:
1623
- - **Primary Mode**: Local LLM ({converter.config.local_model_name})
1624
- - **Fallback**: API ({converter.config.api_model_name})
1625
- - **Status**: {"โœ… Ready" if LLAMA_CPP_AVAILABLE else "โŒ Install llama-cpp-python"}
1626
- - **Search**: {"โœ… Enabled" if BRAVE_KEY else "โŒ Set BSEARCH_API"}
1627
- """, elem_classes=["info-box"])
1628
-
1629
- with gr.Row():
1630
- with gr.Column(scale=3):
1631
- # Input type selector
1632
- input_type_selector = gr.Radio(
1633
- choices=["URL", "PDF", "Keyword"],
1634
- value="URL",
1635
- label="Input Type",
1636
- info="Choose your content source"
1637
- )
1638
-
1639
- # URL input
1640
- url_input = gr.Textbox(
1641
- label="Article URL",
1642
- placeholder="Enter the article URL here...",
1643
- value="",
1644
- visible=True
1645
- )
1646
-
1647
- # PDF upload
1648
- pdf_input = gr.File(
1649
- label="Upload PDF",
1650
- file_types=[".pdf"],
1651
- visible=False
1652
- )
1653
-
1654
- # Keyword input
1655
- keyword_input = gr.Textbox(
1656
- label="Topic/Keyword",
1657
- placeholder="Enter a topic (e.g., 'AI trends', 'quantum computing')",
1658
- value="",
1659
- visible=False,
1660
- info="AI will search and create comprehensive content about this topic"
1661
- )
1662
-
1663
- with gr.Column(scale=1):
1664
- # Language selector
1665
- language_selector = gr.Radio(
1666
- choices=["English", "Korean"],
1667
- value="English",
1668
- label="Language",
1669
- info="Select output language"
1670
- )
1671
-
1672
- mode_selector = gr.Radio(
1673
- choices=["Local", "API"],
1674
- value="Local",
1675
- label="Processing Mode",
1676
- info="Local: On-device | API: Cloud"
1677
- )
1678
-
1679
- # TTS Engine
1680
- with gr.Group():
1681
- gr.Markdown("### TTS Engine")
1682
- tts_selector = gr.Radio(
1683
- choices=["Edge-TTS", "Spark-TTS", "MeloTTS"],
1684
- value="Edge-TTS",
1685
- label="Voice Engine",
1686
- info="Select voice synthesis engine"
1687
- )
1688
-
1689
  gr.Markdown("""
1690
- **๐Ÿ“š Features:**
1691
- - Professional podcast style with expert insights
1692
- - Real-time web search integration
1693
- - 2-4 sentence detailed expert responses
1694
- - Data-driven discussions with examples
1695
- - 12-15 professional exchanges
1696
- """, elem_classes=["info-box"])
1697
-
1698
- convert_btn = gr.Button("๐ŸŽฏ Generate Professional Conversation", variant="primary", size="lg")
1699
-
1700
- with gr.Row():
1701
- with gr.Column():
1702
- conversation_output = gr.Textbox(
1703
- label="Generated Professional Conversation (Editable)",
1704
- lines=35,
1705
- max_lines=70,
1706
- interactive=True,
1707
- placeholder="Professional podcast conversation will appear here...",
1708
- elem_classes=["conversation-output"]
1709
- )
1710
-
1711
- # Audio generation button
1712
  with gr.Row():
1713
- generate_audio_btn = gr.Button("๐ŸŽ™๏ธ Generate Audio from Text", variant="secondary", size="lg")
1714
- gr.Markdown("*Edit the conversation above, then click to generate audio*")
1715
-
1716
- with gr.Column():
1717
- audio_output = gr.Audio(
1718
- label="Professional Podcast Audio",
1719
- type="filepath",
1720
- interactive=False
1721
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1722
 
1723
- # Status message
1724
- status_output = gr.Textbox(
1725
- label="Status",
1726
- interactive=False,
1727
- visible=True,
1728
- elem_id="status_output"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1729
  )
1730
 
1731
-
1732
- gr.Examples(
1733
- examples=[
1734
- ["https://huggingface.co/blog/openfree/cycle-navigator", "URL", "Local", "Edge-TTS", "English"],
1735
- ["quantum computing breakthroughs", "Keyword", "Local", "Edge-TTS", "English"],
1736
- ["https://huggingface.co/papers/2505.14810", "URL", "Local", "Edge-TTS", "English"],
1737
- ["AI ethics and regulation", "Keyword", "Local", "Edge-TTS", "English"],
1738
- ],
1739
- inputs=[url_input, input_type_selector, mode_selector, tts_selector, language_selector],
1740
- outputs=[conversation_output, status_output],
1741
- fn=synthesize_sync,
1742
- cache_examples=False,
1743
- )
1744
-
1745
- # Event handlers
1746
  input_type_selector.change(
1747
  fn=toggle_input_visibility,
1748
  inputs=[input_type_selector],
1749
  outputs=[url_input, pdf_input, keyword_input]
1750
  )
1751
 
 
1752
  language_selector.change(
1753
  fn=update_tts_engine_for_korean,
1754
  inputs=[language_selector],
1755
  outputs=[tts_selector]
1756
  )
1757
 
1758
- # Convert button handler
1759
  def get_article_input(input_type, url_input, pdf_input, keyword_input):
1760
  """Get the appropriate input based on input type"""
1761
  if input_type == "URL":
@@ -1785,7 +1578,6 @@ if __name__ == "__main__":
1785
  demo.queue(api_open=True, default_concurrency_limit=10).launch(
1786
  show_api=True,
1787
  share=False,
1788
- mcp_server=True,
1789
  server_name="0.0.0.0",
1790
  server_port=7860
1791
- )
 
973
  except Exception as e:
974
  raise RuntimeError(f"Failed to extract conversation: {e}")
975
 
976
+ def parse_conversation_text(self, conversation_text: str) -> Dict:
977
  """Parse conversation text back to JSON format"""
978
+ lines = conversation_text.strip().split('\n')
979
  conversation_data = {"conversation": []}
980
 
981
  for line in lines:
 
1327
  choices=["Edge-TTS"],
1328
  value="Edge-TTS",
1329
  label="TTS Engine",
1330
+ info="ํ•œ๊ตญ์–ด๋Š” Edge-TTS๋งŒ ์ง€์›๋ฉ๋‹ˆ๋‹ค",
1331
  interactive=False
1332
  )
1333
  else:
 
1363
  print(f"Failed to download model at startup: {e}")
1364
 
1365
 
1366
+ # Gradio Interface - ๊ฐœ์„ ๋œ ๋ ˆ์ด์•„์›ƒ
1367
+ with gr.Blocks(theme='soft', title="AI Podcast Generator", css="""
1368
+ .container {max-width: 1200px; margin: auto; padding: 20px;}
1369
+ .header-text {text-align: center; margin-bottom: 30px;}
1370
+ .input-group {background: #f7f7f7; padding: 20px; border-radius: 10px; margin-bottom: 20px;}
1371
+ .output-group {background: #f0f0f0; padding: 20px; border-radius: 10px;}
1372
+ .status-box {background: #e8f4f8; padding: 15px; border-radius: 8px; margin-top: 10px;}
1373
+ """) as demo:
1374
+ with gr.Column(elem_classes="container"):
1375
+ # ํ—ค๋”
1376
+ with gr.Row(elem_classes="header-text"):
1377
+ gr.Markdown("""
1378
+ # ๐ŸŽ™๏ธ AI Podcast Generator - Professional Edition
1379
+ ### Convert any article, blog, PDF document, or topic into an engaging professional podcast conversation with in-depth analysis!
1380
+ """)
1381
+
1382
+ # ์ƒํƒœ ํ‘œ์‹œ ์„น์…˜
1383
+ with gr.Row():
1384
+ with gr.Column(scale=1):
1385
+ gr.Markdown(f"""
1386
+ #### ๐Ÿค– System Status
1387
+ - **LLM**: {converter.config.local_model_name.split('.')[0]}
1388
+ - **Fallback**: {converter.config.api_model_name.split('/')[-1]}
1389
+ - **Llama CPP**: {"โœ… Ready" if LLAMA_CPP_AVAILABLE else "โŒ Not Available"}
1390
+ - **Search**: {"โœ… Brave API" if BRAVE_KEY else "โŒ No API"}
1391
+ """)
1392
+ with gr.Column(scale=1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1393
  gr.Markdown("""
1394
+ #### ๐Ÿ“ป Podcast Features
1395
+ - **Length**: 12-15 professional exchanges
1396
+ - **Style**: Expert discussions with data & insights
1397
+ - **Languages**: English & Korean (ํ•œ๊ตญ์–ด)
1398
+ - **Input**: URL, PDF, or Keywords
1399
+ """)
1400
+
1401
+ # ๋ฉ”์ธ ์ž…๋ ฅ ์„น์…˜
1402
+ with gr.Group(elem_classes="input-group"):
 
 
 
 
 
 
 
 
 
 
 
 
 
1403
  with gr.Row():
1404
+ # ์™ผ์ชฝ: ์ž…๋ ฅ ์˜ต์…˜๋“ค
1405
+ with gr.Column(scale=2):
1406
+ # ์ž…๋ ฅ ํƒ€์ž… ์„ ํƒ
1407
+ input_type_selector = gr.Radio(
1408
+ choices=["URL", "PDF", "Keyword"],
1409
+ value="URL",
1410
+ label="๐Ÿ“ฅ Input Type",
1411
+ info="Choose your content source"
1412
+ )
1413
+
1414
+ # URL ์ž…๋ ฅ
1415
+ url_input = gr.Textbox(
1416
+ label="๐Ÿ”— Article URL",
1417
+ placeholder="Enter the article URL here...",
1418
+ value="",
1419
+ visible=True,
1420
+ lines=2
1421
+ )
1422
+
1423
+ # PDF ์—…๋กœ๋“œ
1424
+ pdf_input = gr.File(
1425
+ label="๐Ÿ“„ Upload PDF",
1426
+ file_types=[".pdf"],
1427
+ visible=False
1428
+ )
1429
+
1430
+ # ํ‚ค์›Œ๋“œ ์ž…๋ ฅ
1431
+ keyword_input = gr.Textbox(
1432
+ label="๐Ÿ” Topic/Keyword",
1433
+ placeholder="Enter a topic (e.g., 'AI trends 2024', '์ธ๊ณต์ง€๋Šฅ ์ตœ์‹  ๋™ํ–ฅ')",
1434
+ value="",
1435
+ visible=False,
1436
+ info="System will search and compile latest information",
1437
+ lines=2
1438
+ )
1439
+
1440
+ # ์˜ค๋ฅธ์ชฝ: ์„ค์ • ์˜ต์…˜๋“ค
1441
+ with gr.Column(scale=1):
1442
+ # ์–ธ์–ด ์„ ํƒ
1443
+ language_selector = gr.Radio(
1444
+ choices=["English", "Korean"],
1445
+ value="English",
1446
+ label="๐ŸŒ Language / ์–ธ์–ด",
1447
+ info="Output language"
1448
+ )
1449
+
1450
+ # ์ฒ˜๋ฆฌ ๋ชจ๋“œ
1451
+ mode_selector = gr.Radio(
1452
+ choices=["Local", "API"],
1453
+ value="Local",
1454
+ label="โš™๏ธ Processing Mode",
1455
+ info="Local: On-device | API: Cloud"
1456
+ )
1457
+
1458
+ # TTS ์—”์ง„
1459
+ tts_selector = gr.Radio(
1460
+ choices=["Edge-TTS", "Spark-TTS", "MeloTTS"],
1461
+ value="Edge-TTS",
1462
+ label="๐Ÿ”Š TTS Engine",
1463
+ info="Voice synthesis engine"
1464
+ )
1465
 
1466
+ # ์ƒ์„ฑ ๋ฒ„ํŠผ
1467
+ with gr.Row():
1468
+ convert_btn = gr.Button(
1469
+ "๐ŸŽฏ Generate Professional Conversation",
1470
+ variant="primary",
1471
+ size="lg",
1472
+ scale=1
1473
+ )
1474
+
1475
+ # ์ถœ๋ ฅ ์„น์…˜
1476
+ with gr.Group(elem_classes="output-group"):
1477
+ with gr.Row():
1478
+ # ์™ผ์ชฝ: ๋Œ€ํ™” ํ…์ŠคํŠธ
1479
+ with gr.Column(scale=3):
1480
+ conversation_output = gr.Textbox(
1481
+ label="๐Ÿ’ฌ Generated Professional Conversation (Editable)",
1482
+ lines=25,
1483
+ max_lines=50,
1484
+ interactive=True,
1485
+ placeholder="Professional podcast conversation will appear here...\n์ „๋ฌธ ํŒŸ์บ์ŠคํŠธ ๋Œ€ํ™”๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค...",
1486
+ info="Edit the conversation as needed. Format: 'Speaker Name: Text'"
1487
+ )
1488
+
1489
+ # ์˜ค๋””์˜ค ์ƒ์„ฑ ๋ฒ„ํŠผ
1490
+ with gr.Row():
1491
+ generate_audio_btn = gr.Button(
1492
+ "๐ŸŽ™๏ธ Generate Audio from Text",
1493
+ variant="secondary",
1494
+ size="lg"
1495
+ )
1496
+
1497
+ # ์˜ค๋ฅธ์ชฝ: ์˜ค๋””์˜ค ์ถœ๋ ฅ ๋ฐ ์ƒํƒœ
1498
+ with gr.Column(scale=2):
1499
+ audio_output = gr.Audio(
1500
+ label="๐ŸŽง Professional Podcast Audio",
1501
+ type="filepath",
1502
+ interactive=False
1503
+ )
1504
+
1505
+ status_output = gr.Textbox(
1506
+ label="๐Ÿ“Š Status",
1507
+ interactive=False,
1508
+ lines=3,
1509
+ elem_classes="status-box"
1510
+ )
1511
+
1512
+ # ๋„์›€๋ง
1513
+ gr.Markdown("""
1514
+ #### ๐Ÿ’ก Quick Tips:
1515
+ - **URL**: Paste any article link
1516
+ - **PDF**: Upload documents directly
1517
+ - **Keyword**: Enter topics for AI research
1518
+ - Edit conversation before audio generation
1519
+ - Korean (ํ•œ๊ตญ์–ด) fully supported
1520
+ """)
1521
+
1522
+ # ์˜ˆ์ œ ์„น์…˜
1523
+ with gr.Accordion("๐Ÿ“š Examples", open=False):
1524
+ gr.Examples(
1525
+ examples=[
1526
+ ["https://huggingface.co/blog/openfree/cycle-navigator", "URL", "Local", "Edge-TTS", "English"],
1527
+ ["quantum computing breakthroughs", "Keyword", "Local", "Edge-TTS", "English"],
1528
+ ["https://huggingface.co/papers/2505.14810", "URL", "Local", "Edge-TTS", "Korean"],
1529
+ ["์ธ๊ณต์ง€๋Šฅ ์œค๋ฆฌ์™€ ๊ทœ์ œ", "Keyword", "Local", "Edge-TTS", "Korean"],
1530
+ ],
1531
+ inputs=[url_input, input_type_selector, mode_selector, tts_selector, language_selector],
1532
+ outputs=[conversation_output, status_output],
1533
+ fn=synthesize_sync,
1534
+ cache_examples=False,
1535
  )
1536
 
1537
+ # Input type change handler
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1538
  input_type_selector.change(
1539
  fn=toggle_input_visibility,
1540
  inputs=[input_type_selector],
1541
  outputs=[url_input, pdf_input, keyword_input]
1542
  )
1543
 
1544
+ # ์–ธ์–ด ๋ณ€๊ฒฝ ์‹œ TTS ์—”์ง„ ์˜ต์…˜ ์—…๋ฐ์ดํŠธ
1545
  language_selector.change(
1546
  fn=update_tts_engine_for_korean,
1547
  inputs=[language_selector],
1548
  outputs=[tts_selector]
1549
  )
1550
 
1551
+ # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
1552
  def get_article_input(input_type, url_input, pdf_input, keyword_input):
1553
  """Get the appropriate input based on input type"""
1554
  if input_type == "URL":
 
1578
  demo.queue(api_open=True, default_concurrency_limit=10).launch(
1579
  show_api=True,
1580
  share=False,
 
1581
  server_name="0.0.0.0",
1582
  server_port=7860
1583
+ )