File size: 4,657 Bytes
5ec72cd
 
 
38bbab9
5ec72cd
 
 
 
259349d
38bbab9
5ec72cd
 
 
 
 
 
 
0bfc688
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259349d
5ec72cd
 
0bfc688
5ec72cd
 
 
 
259349d
0bfc688
5ec72cd
 
1ec6fec
5ec72cd
 
 
1ec6fec
 
 
 
 
 
 
 
 
5ec72cd
 
 
f70205c
5ec72cd
 
f70205c
5ec72cd
38bbab9
 
 
 
 
 
 
 
 
 
 
 
5ec72cd
259349d
 
 
 
5ec72cd
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
from __future__ import annotations

import json
import os
from pathlib import Path

import streamlit as st

from src.api.session_bootstrap import ensure_session_bootstrap_server
from src.api.streamlit_routes import build_session_bootstrap_path
from src.core.settings import settings

UI_DIR = Path(__file__).parent / "ui"
INDEX_TEMPLATE = UI_DIR / "index.html"
MAIN_JS = UI_DIR / "main.js"


def extract_display_name(model_id: str, provider: str) -> str:
    """Extract human-readable display name from model ID."""
    # Get the model name (part after '/' if present)
    if "/" in model_id:
        name = model_id.split("/")[-1]
    else:
        name = model_id

    # Special cases for well-known models
    if "moonshine" in name.lower():
        return "Moonshine STT"
    elif "qwen" in name.lower():
        return name  # Keep format like "Qwen2.5-3B-Instruct"
    elif "parakeet" in name.lower():
        return "NVIDIA Parakeet"
    elif "llama" in name.lower():
        parts = name.split("-")
        return f"Llama {parts[1]}" if len(parts) > 1 else name

    # Default: basic cleanup
    return name.replace("-", " ").title()


def generate_footer_html() -> str:
    """Generate dynamic 'Powered by' footer HTML based on current settings."""
    # STT Component
    stt_provider = settings.stt.STT_PROVIDER.lower()
    if stt_provider == "moonshine":
        stt_model_id = settings.stt.MOONSHINE_MODEL_ID
        stt_url = f"https://huggingface.co/{stt_model_id}"
        stt_display = extract_display_name(stt_model_id, "moonshine")
    elif stt_provider == "nvidia":
        stt_url = "https://build.nvidia.com/"
        stt_display = extract_display_name(settings.stt.NVIDIA_STT_MODEL, "nvidia")
    else:
        stt_url = "#"
        stt_display = f"Unknown STT ({stt_provider})"

    # LLM Component
    llm_provider = settings.llm.LLM_PROVIDER.lower()
    if llm_provider == "huggingface":
        llm_model_id = settings.llm.HUGGINGFACE_MODEL_ID
        llm_url = f"https://huggingface.co/{llm_model_id}"
        llm_display = extract_display_name(llm_model_id, "huggingface")
    elif llm_provider == "nvidia":
        llm_url = "https://build.nvidia.com/"
        llm_display = extract_display_name(settings.llm.NVIDIA_MODEL, "nvidia")
    else:
        llm_url = "#"
        llm_display = f"Unknown LLM ({llm_provider})"

    # Build footer HTML
    return f"""Powered by open-source core components:
        <a href="{stt_url}" target="_blank" rel="noopener noreferrer">{stt_display}</a> 路
        <a href="{llm_url}" target="_blank" rel="noopener noreferrer">{llm_display}</a> 路
        <a href="https://huggingface.co/kyutai/pocket-tts" target="_blank" rel="noopener noreferrer">PocketTTS</a> 路
        <a href="https://livekit.io/" target="_blank" rel="noopener noreferrer">LiveKit</a>"""


def render_client(*, livekit_url: str, session_bootstrap_url: str) -> None:
    template = INDEX_TEMPLATE.read_text(encoding="utf-8")
    js = MAIN_JS.read_text(encoding="utf-8")
    footer_html = generate_footer_html()

    html = (
        template.replace("{{MAIN_JS}}", js)
        .replace("{{LIVEKIT_URL_JSON}}", json.dumps(livekit_url))
        .replace("{{SESSION_BOOTSTRAP_URL_JSON}}", json.dumps(session_bootstrap_url))
        .replace("{{FOOTER_POWERED_BY}}", footer_html)
    )

    st.components.v1.html(html, height=900, scrolling=False)


def main() -> None:
    st.set_page_config(page_title="Open Voice Agent", layout="wide")
    st.markdown(
        "<style>"
        "header {visibility: hidden;} "
        ".stMainBlockContainer {padding-top: 1rem; padding-bottom: 0;} "
        "iframe {border: 1px solid #252d3f; border-radius: 12px;}"
        "</style>",
        unsafe_allow_html=True,
    )

    if not settings.livekit.LIVEKIT_URL:
        st.error("LIVEKIT_URL is not set in the environment.")
        st.stop()
    if not settings.livekit.LIVEKIT_API_KEY or not settings.livekit.LIVEKIT_API_SECRET:
        st.error("LIVEKIT_API_KEY or LIVEKIT_API_SECRET is not set.")
        st.stop()

    use_streamlit_route = os.getenv(
        "OPEN_VOICE_USE_STREAMLIT_BOOTSTRAP_ROUTE", "0"
    ).lower() in {"1", "true", "yes"}

    if use_streamlit_route:
        session_bootstrap_url = build_session_bootstrap_path()
    else:
        try:
            session_bootstrap_url = ensure_session_bootstrap_server()
        except Exception as exc:
            st.error(f"Failed to start session bootstrap service: {exc}")
            st.stop()

    render_client(
        livekit_url=settings.livekit.LIVEKIT_URL,
        session_bootstrap_url=session_bootstrap_url,
    )


if __name__ == "__main__":
    main()