Improve UI contrast: high-visibility theme and status cards.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -66,15 +66,15 @@ def ui_lock_speaker(audio_file, sample_transcript: str) -> tuple[str, str]:
|
|||||||
"""【音色锁定】从参考人声提取并保存 Speaker Embedding。"""
|
"""【音色锁定】从参考人声提取并保存 Speaker Embedding。"""
|
||||||
path = _save_upload(audio_file)
|
path = _save_upload(audio_file)
|
||||||
if not path:
|
if not path:
|
||||||
return "请上传 10-30 秒干净参考人声(wav/mp3 均可)。", ui_speaker_status()
|
return "请上传 10-30 秒干净参考人声(wav/mp3 均可)。", ui_speaker_status_html()
|
||||||
|
|
||||||
ok, msg = save_fixed_speaker(path, sample_transcript or "")
|
ok, msg = save_fixed_speaker(path, sample_transcript or "")
|
||||||
result = msg if ok else f"❌ {msg}"
|
result = msg if ok else f"❌ {msg}"
|
||||||
return result, ui_speaker_status()
|
return result, ui_speaker_status_html()
|
||||||
|
|
||||||
|
|
||||||
def ui_speaker_status() -> str:
|
def ui_speaker_status() -> str:
|
||||||
"""刷新音色状态。"""
|
"""刷新音色状态(纯文本,供日志框使用)。"""
|
||||||
ok, msg = speaker_is_ready()
|
ok, msg = speaker_is_ready()
|
||||||
return f"✅ {msg}" if ok else f"⚠️ {msg}"
|
return f"✅ {msg}" if ok else f"⚠️ {msg}"
|
||||||
|
|
||||||
@@ -180,46 +180,218 @@ def ui_full_pipeline(
|
|||||||
# Gradio 界面
|
# Gradio 界面
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
CUSTOM_CSS = """
|
CUSTOM_CSS = """
|
||||||
/* 硬核暗黑科技风 */
|
/* ========== 高对比度暗色主题(确保文字清晰可读) ========== */
|
||||||
.gradio-container {
|
.gradio-container {
|
||||||
background: linear-gradient(160deg, #0a0a0f 0%, #12121a 40%, #0d0d12 100%) !important;
|
background: #0f1419 !important;
|
||||||
color: #c8c8d0 !important;
|
color: #eef2f7 !important;
|
||||||
|
font-size: 15px !important;
|
||||||
|
max-width: 1400px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全局文字 */
|
||||||
|
.gradio-container p,
|
||||||
|
.gradio-container span,
|
||||||
|
.gradio-container label,
|
||||||
|
.gradio-container .prose,
|
||||||
|
.gradio-container .markdown-text,
|
||||||
|
.gradio-container .md {
|
||||||
|
color: #eef2f7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题 */
|
||||||
|
.gradio-container h1 {
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-size: 1.75rem !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
}
|
||||||
|
.gradio-container h2,
|
||||||
|
.gradio-container h3 {
|
||||||
|
color: #dbeafe !important;
|
||||||
|
font-size: 1.15rem !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签 */
|
||||||
|
.gradio-container .block-label,
|
||||||
|
.gradio-container label,
|
||||||
|
.gradio-container .label-wrap span {
|
||||||
|
color: #93c5fd !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 0.95rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框 / 文本域 */
|
||||||
|
.gradio-container textarea,
|
||||||
|
.gradio-container input[type="text"],
|
||||||
|
.gradio-container .wrap textarea,
|
||||||
|
.gradio-container .wrap input {
|
||||||
|
color: #ffffff !important;
|
||||||
|
background: #1a2332 !important;
|
||||||
|
border: 1px solid #4b5563 !important;
|
||||||
|
font-size: 0.95rem !important;
|
||||||
|
line-height: 1.6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradio-container textarea::placeholder,
|
||||||
|
.gradio-container input::placeholder {
|
||||||
|
color: #9ca3af !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 只读日志框 */
|
||||||
|
.gradio-container .wrap .readonly textarea {
|
||||||
|
background: #111827 !important;
|
||||||
|
color: #e5e7eb !important;
|
||||||
|
border-color: #374151 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab 标签页 */
|
||||||
|
.gradio-container .tab-nav button {
|
||||||
|
color: #9ca3af !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 0.95rem !important;
|
||||||
|
padding: 10px 18px !important;
|
||||||
|
}
|
||||||
|
.gradio-container .tab-nav button.selected {
|
||||||
|
color: #ffffff !important;
|
||||||
|
background: #1e3a5f !important;
|
||||||
|
border-bottom: 3px solid #3b82f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮 */
|
||||||
|
.gradio-container button.primary,
|
||||||
|
.gradio-container .primary {
|
||||||
|
background: #2563eb !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 0.95rem !important;
|
||||||
|
border: 1px solid #3b82f6 !important;
|
||||||
|
}
|
||||||
|
.gradio-container button.primary:hover {
|
||||||
|
background: #1d4ed8 !important;
|
||||||
|
}
|
||||||
|
.gradio-container button.secondary,
|
||||||
|
.gradio-container button:not(.primary) {
|
||||||
|
color: #e5e7eb !important;
|
||||||
|
background: #374151 !important;
|
||||||
|
border: 1px solid #6b7280 !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部信息面板 */
|
||||||
.dark-panel {
|
.dark-panel {
|
||||||
border: 1px solid #2a2a35;
|
border: 1px solid #3b82f6 !important;
|
||||||
border-radius: 8px;
|
border-radius: 10px !important;
|
||||||
padding: 16px;
|
padding: 18px 20px !important;
|
||||||
background: rgba(18, 18, 26, 0.85);
|
background: #1a2332 !important;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 16px !important;
|
||||||
}
|
}
|
||||||
h1, h2, h3 { color: #e8e8f0 !important; letter-spacing: 0.05em; }
|
.dark-panel code {
|
||||||
.status-bar {
|
color: #93c5fd !important;
|
||||||
font-family: 'Consolas', 'Monaco', monospace;
|
background: #0f172a !important;
|
||||||
font-size: 0.85em;
|
padding: 2px 6px !important;
|
||||||
color: #7a7a90;
|
border-radius: 4px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 状态卡片 */
|
||||||
|
.status-card {
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
margin: 4px 0;
|
||||||
|
border-left: 5px solid #6b7280;
|
||||||
|
background: #1f2937;
|
||||||
|
min-height: 72px;
|
||||||
|
}
|
||||||
|
.status-card .status-title {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #93c5fd !important;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
.status-card .status-body {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: #f3f4f6 !important;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.status-ok {
|
||||||
|
border-left-color: #22c55e !important;
|
||||||
|
background: #14291a !important;
|
||||||
|
}
|
||||||
|
.status-ok .status-body { color: #bbf7d0 !important; }
|
||||||
|
.status-warn {
|
||||||
|
border-left-color: #f59e0b !important;
|
||||||
|
background: #2a2010 !important;
|
||||||
|
}
|
||||||
|
.status-warn .status-body { color: #fde68a !important; }
|
||||||
|
.status-err {
|
||||||
|
border-left-color: #ef4444 !important;
|
||||||
|
background: #2a1515 !important;
|
||||||
|
}
|
||||||
|
.status-err .status-body { color: #fecaca !important; }
|
||||||
|
|
||||||
|
/* 音频上传区 */
|
||||||
|
.gradio-container .audio-container,
|
||||||
|
.gradio-container .upload-container {
|
||||||
|
border: 2px dashed #4b5563 !important;
|
||||||
|
background: #1a2332 !important;
|
||||||
|
}
|
||||||
|
|
||||||
footer { visibility: hidden; }
|
footer { visibility: hidden; }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _status_html(title: str, message: str, level: str = "warn") -> str:
|
||||||
|
"""生成高对比度状态卡片 HTML。level: ok | warn | err"""
|
||||||
|
icons = {"ok": "✅", "warn": "⚠️", "err": "❌"}
|
||||||
|
icon = icons.get(level, "ℹ️")
|
||||||
|
# 去掉 message 里重复的 emoji,避免双图标
|
||||||
|
clean = message.lstrip("✅❌⚠️ ").strip()
|
||||||
|
return (
|
||||||
|
f'<div class="status-card status-{level}">'
|
||||||
|
f'<div class="status-title">{icon} {title}</div>'
|
||||||
|
f'<div class="status-body">{clean}</div>'
|
||||||
|
f"</div>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ui_check_ollama_html() -> str:
|
||||||
|
ok, msg = check_ollama_health()
|
||||||
|
return _status_html("Ollama 节点", msg, "ok" if ok else "err")
|
||||||
|
|
||||||
|
|
||||||
|
def ui_speaker_status_html() -> str:
|
||||||
|
ok, msg = speaker_is_ready()
|
||||||
|
return _status_html("音色状态", msg, "ok" if ok else "warn")
|
||||||
|
|
||||||
|
|
||||||
def build_theme() -> gr.themes.Base:
|
def build_theme() -> gr.themes.Base:
|
||||||
"""构建暗黑科技风 Monochrome 主题(Gradio 6.0 需在 launch() 传入)。"""
|
"""高对比度暗色主题(Gradio 6.0 需在 launch() 传入)。"""
|
||||||
return gr.themes.Monochrome(
|
return gr.themes.Base(
|
||||||
primary_hue="slate",
|
primary_hue="blue",
|
||||||
secondary_hue="gray",
|
secondary_hue="blue",
|
||||||
neutral_hue="slate",
|
neutral_hue="slate",
|
||||||
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
|
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
|
||||||
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "Consolas", "monospace"],
|
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "Consolas", "monospace"],
|
||||||
).set(
|
).set(
|
||||||
body_background_fill="#0a0a0f",
|
body_background_fill="#0f1419",
|
||||||
body_background_fill_dark="#0a0a0f",
|
body_background_fill_dark="#0f1419",
|
||||||
block_background_fill="#12121a",
|
body_text_color="#eef2f7",
|
||||||
block_background_fill_dark="#12121a",
|
body_text_color_dark="#eef2f7",
|
||||||
block_border_color="#2a2a35",
|
block_background_fill="#1a2332",
|
||||||
block_label_text_color="#9090a0",
|
block_background_fill_dark="#1a2332",
|
||||||
input_background_fill="#1a1a24",
|
block_border_color="#4b5563",
|
||||||
button_primary_background_fill="#3a3a50",
|
block_title_text_color="#ffffff",
|
||||||
button_primary_background_fill_hover="#4a4a60",
|
block_label_text_color="#93c5fd",
|
||||||
|
input_background_fill="#1a2332",
|
||||||
|
input_background_fill_dark="#1a2332",
|
||||||
|
button_primary_background_fill="#2563eb",
|
||||||
|
button_primary_background_fill_hover="#1d4ed8",
|
||||||
|
button_primary_text_color="#ffffff",
|
||||||
|
button_secondary_background_fill="#374151",
|
||||||
|
button_secondary_text_color="#e5e7eb",
|
||||||
|
border_color_primary="#3b82f6",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -231,9 +403,14 @@ def build_app() -> gr.Blocks:
|
|||||||
gr.Markdown(
|
gr.Markdown(
|
||||||
f"""
|
f"""
|
||||||
# ⚡ Trading Studio
|
# ⚡ Trading Studio
|
||||||
|
|
||||||
**本地量化交易复盘 → B 站配音生产流水线**
|
**本地量化交易复盘 → B 站配音生产流水线**
|
||||||
|
|
||||||
`Whisper(CUDA)` → `Gemma4 @ {OLLAMA_URL}` → `ChatTTS(CUDA)`
|
| 模块 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| Whisper | 本地 GPU 语音识别 |
|
||||||
|
| Gemma4 | `{MODEL_NAME}` @ `{OLLAMA_URL.replace('/api/chat', '')}` |
|
||||||
|
| ChatTTS | 本地 GPU 固定音色合成 |
|
||||||
|
|
||||||
> 仓库: [{GIT_REPO_URL}]({GIT_REPO_URL})
|
> 仓库: [{GIT_REPO_URL}]({GIT_REPO_URL})
|
||||||
""",
|
""",
|
||||||
@@ -241,24 +418,12 @@ def build_app() -> gr.Blocks:
|
|||||||
)
|
)
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
ollama_status = gr.Textbox(
|
ollama_status = gr.HTML(value=_status_html("Ollama 节点", "正在检测...", "warn"))
|
||||||
label="Ollama 节点",
|
speaker_status = gr.HTML(value=_status_html("音色状态", "正在检测...", "warn"))
|
||||||
value="检测中...",
|
refresh_btn = gr.Button("🔄 刷新状态", variant="secondary", scale=0)
|
||||||
interactive=False,
|
|
||||||
scale=3,
|
|
||||||
elem_classes=["status-bar"],
|
|
||||||
)
|
|
||||||
speaker_status = gr.Textbox(
|
|
||||||
label="音色状态",
|
|
||||||
value="检测中...",
|
|
||||||
interactive=False,
|
|
||||||
scale=2,
|
|
||||||
elem_classes=["status-bar"],
|
|
||||||
)
|
|
||||||
refresh_btn = gr.Button("🔄 刷新状态", scale=1)
|
|
||||||
|
|
||||||
refresh_btn.click(
|
refresh_btn.click(
|
||||||
fn=lambda: (ui_check_ollama(), ui_speaker_status()),
|
fn=lambda: (ui_check_ollama_html(), ui_speaker_status_html()),
|
||||||
outputs=[ollama_status, speaker_status],
|
outputs=[ollama_status, speaker_status],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -360,7 +525,7 @@ def build_app() -> gr.Blocks:
|
|||||||
)
|
)
|
||||||
|
|
||||||
demo.load(
|
demo.load(
|
||||||
fn=lambda: (ui_check_ollama(), ui_speaker_status()),
|
fn=lambda: (ui_check_ollama_html(), ui_speaker_status_html()),
|
||||||
outputs=[ollama_status, speaker_status],
|
outputs=[ollama_status, speaker_status],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user