Update app.py
Browse files
app.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
# File: main/app.py
|
| 2 |
-
# Purpose: One Space that offers
|
| 3 |
# 1) Fetch — extract relevant page content (title, metadata, clean text, hyperlinks)
|
| 4 |
# 2) DuckDuckGo Search — compact JSONL search output (short keys to minimize tokens)
|
| 5 |
# 3) Python Code Executor — run Python code and capture stdout/errors
|
|
|
|
| 6 |
|
| 7 |
from __future__ import annotations
|
| 8 |
|
|
@@ -19,6 +20,18 @@ from readability import Document
|
|
| 19 |
from urllib.parse import urljoin, urldefrag, urlparse
|
| 20 |
from duckduckgo_search import DDGS
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
# ==============================
|
| 24 |
# Fetch: HTTP + extraction utils
|
|
@@ -431,8 +444,114 @@ def Execute_Python(code: Annotated[str, "Python source code to run; stdout is ca
|
|
| 431 |
sys.stdout = old_stdout
|
| 432 |
|
| 433 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
# ======================
|
| 435 |
-
# UI:
|
| 436 |
# ======================
|
| 437 |
|
| 438 |
# --- Fetch tab (compact controllable extraction) ---
|
|
@@ -510,23 +629,13 @@ code_interface = gr.Interface(
|
|
| 510 |
theme="Nymbo/Nymbo_Theme",
|
| 511 |
)
|
| 512 |
|
| 513 |
-
|
| 514 |
-
demo = gr.TabbedInterface(
|
| 515 |
-
interface_list=[fetch_interface, concise_interface, code_interface],
|
| 516 |
-
tab_names=[
|
| 517 |
-
"Fetch Webpage",
|
| 518 |
-
"DuckDuckGo Search",
|
| 519 |
-
"Python Code Executor",
|
| 520 |
-
],
|
| 521 |
-
title="Tools MCP",
|
| 522 |
-
theme="Nymbo/Nymbo_Theme",
|
| 523 |
-
css="""
|
| 524 |
.gradio-container h1 {
|
| 525 |
text-align: center;
|
| 526 |
}
|
| 527 |
/* Default: add subtitle under titles */
|
| 528 |
.gradio-container h1::after {
|
| 529 |
-
content: "Fetch Webpage | Search DuckDuckGo | Code Interpreter";
|
| 530 |
display: block;
|
| 531 |
font-size: 1rem;
|
| 532 |
font-weight: 500;
|
|
@@ -535,10 +644,45 @@ demo = gr.TabbedInterface(
|
|
| 535 |
}
|
| 536 |
|
| 537 |
/* But remove it inside tab panels so it doesn't duplicate under each tool title */
|
| 538 |
-
.gradio-container [role
|
| 539 |
content: none !important;
|
| 540 |
}
|
| 541 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
)
|
| 543 |
|
| 544 |
# Launch the UI and expose all functions as MCP tools in one server
|
|
|
|
| 1 |
# File: main/app.py
|
| 2 |
+
# Purpose: One Space that offers four tools/tabs (all exposed as MCP tools):
|
| 3 |
# 1) Fetch — extract relevant page content (title, metadata, clean text, hyperlinks)
|
| 4 |
# 2) DuckDuckGo Search — compact JSONL search output (short keys to minimize tokens)
|
| 5 |
# 3) Python Code Executor — run Python code and capture stdout/errors
|
| 6 |
+
# 4) Kokoro TTS — synthesize speech from text using Kokoro-82M
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
|
|
|
| 20 |
from urllib.parse import urljoin, urldefrag, urlparse
|
| 21 |
from duckduckgo_search import DDGS
|
| 22 |
|
| 23 |
+
# Optional imports for Kokoro TTS (loaded lazily)
|
| 24 |
+
import numpy as np
|
| 25 |
+
try:
|
| 26 |
+
import torch # type: ignore
|
| 27 |
+
except Exception: # pragma: no cover - optional dependency
|
| 28 |
+
torch = None # type: ignore
|
| 29 |
+
try:
|
| 30 |
+
from kokoro import KModel, KPipeline # type: ignore
|
| 31 |
+
except Exception: # pragma: no cover - optional dependency
|
| 32 |
+
KModel = None # type: ignore
|
| 33 |
+
KPipeline = None # type: ignore
|
| 34 |
+
|
| 35 |
|
| 36 |
# ==============================
|
| 37 |
# Fetch: HTTP + extraction utils
|
|
|
|
| 444 |
sys.stdout = old_stdout
|
| 445 |
|
| 446 |
|
| 447 |
+
# ==========================
|
| 448 |
+
# Kokoro TTS (MCP tool #4)
|
| 449 |
+
# ==========================
|
| 450 |
+
|
| 451 |
+
_KOKORO_STATE = {
|
| 452 |
+
"initialized": False,
|
| 453 |
+
"device": "cpu",
|
| 454 |
+
"model": None,
|
| 455 |
+
"pipelines": {},
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def _init_kokoro() -> None:
|
| 460 |
+
"""Lazy-initialize Kokoro model and pipelines on first use.
|
| 461 |
+
|
| 462 |
+
Tries CUDA if torch is present and available; falls back to CPU. Keeps a
|
| 463 |
+
minimal English pipeline and custom lexicon tweak for the word "kokoro".
|
| 464 |
+
"""
|
| 465 |
+
if _KOKORO_STATE["initialized"]:
|
| 466 |
+
return
|
| 467 |
+
|
| 468 |
+
if KModel is None or KPipeline is None:
|
| 469 |
+
raise RuntimeError(
|
| 470 |
+
"Kokoro is not installed. Please install the 'kokoro' package (>=0.9.4)."
|
| 471 |
+
)
|
| 472 |
+
|
| 473 |
+
device = "cpu"
|
| 474 |
+
if torch is not None:
|
| 475 |
+
try:
|
| 476 |
+
if torch.cuda.is_available(): # type: ignore[attr-defined]
|
| 477 |
+
device = "cuda"
|
| 478 |
+
except Exception:
|
| 479 |
+
device = "cpu"
|
| 480 |
+
|
| 481 |
+
model = KModel().to(device).eval()
|
| 482 |
+
pipelines = {"a": KPipeline(lang_code="a", model=False)}
|
| 483 |
+
# Custom pronunciation
|
| 484 |
+
try:
|
| 485 |
+
pipelines["a"].g2p.lexicon.golds["kokoro"] = "kˈOkəɹO"
|
| 486 |
+
except Exception:
|
| 487 |
+
pass
|
| 488 |
+
|
| 489 |
+
_KOKORO_STATE.update(
|
| 490 |
+
{
|
| 491 |
+
"initialized": True,
|
| 492 |
+
"device": device,
|
| 493 |
+
"model": model,
|
| 494 |
+
"pipelines": pipelines,
|
| 495 |
+
}
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def Kokoro_TextToAudio( # <-- MCP tool #4 (Kokoro TTS)
|
| 500 |
+
text: Annotated[str, "The text to synthesize (English)."],
|
| 501 |
+
speed: Annotated[float, "Speech speed multiplier in 0.5–2.0; 1.0 = normal speed."] = 1.0,
|
| 502 |
+
voice: Annotated[str, "Voice identifier. Example: 'af_heart' (US English, female, Heart)."] = "af_heart",
|
| 503 |
+
) -> Tuple[int, np.ndarray]:
|
| 504 |
+
"""
|
| 505 |
+
Synthesize speech from text using the Kokoro-82M model.
|
| 506 |
+
|
| 507 |
+
This function returns raw audio suitable for a Gradio Audio component and is
|
| 508 |
+
also exposed as an MCP tool (per the latest Hugging Face/Gradio MCP docs, a
|
| 509 |
+
tool is created for each function wired into your app; docstrings and type
|
| 510 |
+
hints are used to describe the tool).
|
| 511 |
+
|
| 512 |
+
Args:
|
| 513 |
+
text: The text to synthesize (English).
|
| 514 |
+
speed: Speech speed multiplier in 0.5–2.0; 1.0 = normal speed.
|
| 515 |
+
voice: Voice identifier. Example: 'af_heart' (US English, female, Heart).
|
| 516 |
+
|
| 517 |
+
Returns:
|
| 518 |
+
A tuple of (sample_rate_hz, audio_waveform) where:
|
| 519 |
+
- sample_rate_hz: int sample rate in Hz (24_000)
|
| 520 |
+
- audio_waveform: numpy.ndarray float32 mono waveform in range [-1, 1]
|
| 521 |
+
|
| 522 |
+
Notes:
|
| 523 |
+
- Requires the 'kokoro' package (>=0.9.4). If unavailable, an error is
|
| 524 |
+
raised with installation guidance.
|
| 525 |
+
- Runs on CUDA if available; otherwise CPU.
|
| 526 |
+
"""
|
| 527 |
+
if not text or not text.strip():
|
| 528 |
+
raise gr.Error("Please provide non-empty text to synthesize.")
|
| 529 |
+
|
| 530 |
+
_init_kokoro()
|
| 531 |
+
model = _KOKORO_STATE["model"]
|
| 532 |
+
pipelines = _KOKORO_STATE["pipelines"]
|
| 533 |
+
|
| 534 |
+
pipeline = pipelines.get("a")
|
| 535 |
+
if pipeline is None:
|
| 536 |
+
raise gr.Error("Kokoro English pipeline not initialized.")
|
| 537 |
+
|
| 538 |
+
pack = pipeline.load_voice(voice)
|
| 539 |
+
# Generate using the last reference state from the current phoneme sequence
|
| 540 |
+
for _, ps, _ in pipeline(text, voice, speed):
|
| 541 |
+
ref_s = pack[len(ps) - 1]
|
| 542 |
+
try:
|
| 543 |
+
audio = model(ps, ref_s, float(speed))
|
| 544 |
+
except Exception as e: # propagate as UI-friendly error
|
| 545 |
+
raise gr.Error(f"Error generating audio: {str(e)}")
|
| 546 |
+
# Return 24 kHz mono waveform
|
| 547 |
+
return 24_000, audio.detach().cpu().numpy()
|
| 548 |
+
|
| 549 |
+
# If pipeline produced no segments
|
| 550 |
+
raise gr.Error("No audio was generated (empty synthesis result).")
|
| 551 |
+
|
| 552 |
+
|
| 553 |
# ======================
|
| 554 |
+
# UI: four-tab interface
|
| 555 |
# ======================
|
| 556 |
|
| 557 |
# --- Fetch tab (compact controllable extraction) ---
|
|
|
|
| 629 |
theme="Nymbo/Nymbo_Theme",
|
| 630 |
)
|
| 631 |
|
| 632 |
+
CSS_STYLES = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
.gradio-container h1 {
|
| 634 |
text-align: center;
|
| 635 |
}
|
| 636 |
/* Default: add subtitle under titles */
|
| 637 |
.gradio-container h1::after {
|
| 638 |
+
content: "Fetch Webpage | Search DuckDuckGo | Code Interpreter | Kokoro TTS";
|
| 639 |
display: block;
|
| 640 |
font-size: 1rem;
|
| 641 |
font-weight: 500;
|
|
|
|
| 644 |
}
|
| 645 |
|
| 646 |
/* But remove it inside tab panels so it doesn't duplicate under each tool title */
|
| 647 |
+
.gradio-container [role=\"tabpanel\"] h1::after {
|
| 648 |
content: none !important;
|
| 649 |
}
|
| 650 |
+
"""
|
| 651 |
+
|
| 652 |
+
# --- Kokoro TTS tab (text to speech) ---
|
| 653 |
+
kokoro_interface = gr.Interface(
|
| 654 |
+
fn=Kokoro_TextToAudio,
|
| 655 |
+
inputs=[
|
| 656 |
+
gr.Textbox(label="Text", placeholder="Type text to synthesize…", lines=4),
|
| 657 |
+
gr.Slider(minimum=0.5, maximum=2.0, value=1.0, step=0.1, label="Speed"),
|
| 658 |
+
gr.Textbox(label="Voice", value="af_heart", placeholder="e.g., af_heart"),
|
| 659 |
+
],
|
| 660 |
+
outputs=gr.Audio(label="Audio", type="numpy"),
|
| 661 |
+
title="Kokoro TTS",
|
| 662 |
+
description=(
|
| 663 |
+
"<div style=\"text-align:center\">Synthesize English speech with Kokoro-82M. Requires the 'kokoro' package."
|
| 664 |
+
" Exposed as an MCP tool with clear type hints and docstrings per the latest HF/Gradio MCP guidance.</div>"
|
| 665 |
+
),
|
| 666 |
+
api_description=(
|
| 667 |
+
"Synthesize speech from text using Kokoro-82M. Returns (sample_rate, waveform) suitable for playback."
|
| 668 |
+
" Parameters: text (str), speed (float 0.5–2.0), voice (str)."
|
| 669 |
+
),
|
| 670 |
+
allow_flagging="never",
|
| 671 |
+
theme="Nymbo/Nymbo_Theme",
|
| 672 |
+
)
|
| 673 |
+
|
| 674 |
+
# Build tabbed app including Kokoro
|
| 675 |
+
demo = gr.TabbedInterface(
|
| 676 |
+
interface_list=[fetch_interface, concise_interface, code_interface, kokoro_interface],
|
| 677 |
+
tab_names=[
|
| 678 |
+
"Fetch Webpage",
|
| 679 |
+
"DuckDuckGo Search",
|
| 680 |
+
"Python Code Executor",
|
| 681 |
+
"Kokoro TTS",
|
| 682 |
+
],
|
| 683 |
+
title="Tools MCP",
|
| 684 |
+
theme="Nymbo/Nymbo_Theme",
|
| 685 |
+
css=CSS_STYLES,
|
| 686 |
)
|
| 687 |
|
| 688 |
# Launch the UI and expose all functions as MCP tools in one server
|