Improve UI contrast: high-visibility theme and status cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-12 14:20:05 +08:00
parent fc96f834a0
commit e11caa59ab
+212 -47
View File
@@ -66,15 +66,15 @@ def ui_lock_speaker(audio_file, sample_transcript: str) -> tuple[str, str]:
"""【音色锁定】从参考人声提取并保存 Speaker Embedding。"""
path = _save_upload(audio_file)
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 "")
result = msg if ok else f"{msg}"
return result, ui_speaker_status()
return result, ui_speaker_status_html()
def ui_speaker_status() -> str:
"""刷新音色状态。"""
"""刷新音色状态(纯文本,供日志框使用)"""
ok, msg = speaker_is_ready()
return f"{msg}" if ok else f"⚠️ {msg}"
@@ -180,46 +180,218 @@ def ui_full_pipeline(
# Gradio 界面
# ---------------------------------------------------------------------------
CUSTOM_CSS = """
/* 硬核暗黑科技风 */
/* ========== 高对比度暗色主题(确保文字清晰可读) ========== */
.gradio-container {
background: linear-gradient(160deg, #0a0a0f 0%, #12121a 40%, #0d0d12 100%) !important;
color: #c8c8d0 !important;
background: #0f1419 !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 {
border: 1px solid #2a2a35;
border-radius: 8px;
padding: 16px;
background: rgba(18, 18, 26, 0.85);
margin-bottom: 12px;
border: 1px solid #3b82f6 !important;
border-radius: 10px !important;
padding: 18px 20px !important;
background: #1a2332 !important;
margin-bottom: 16px !important;
}
h1, h2, h3 { color: #e8e8f0 !important; letter-spacing: 0.05em; }
.status-bar {
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.85em;
color: #7a7a90;
.dark-panel code {
color: #93c5fd !important;
background: #0f172a !important;
padding: 2px 6px !important;
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; }
"""
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:
"""构建暗黑科技风 Monochrome 主题(Gradio 6.0 需在 launch() 传入)。"""
return gr.themes.Monochrome(
primary_hue="slate",
secondary_hue="gray",
"""高对比度暗色主题(Gradio 6.0 需在 launch() 传入)。"""
return gr.themes.Base(
primary_hue="blue",
secondary_hue="blue",
neutral_hue="slate",
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "Consolas", "monospace"],
).set(
body_background_fill="#0a0a0f",
body_background_fill_dark="#0a0a0f",
block_background_fill="#12121a",
block_background_fill_dark="#12121a",
block_border_color="#2a2a35",
block_label_text_color="#9090a0",
input_background_fill="#1a1a24",
button_primary_background_fill="#3a3a50",
button_primary_background_fill_hover="#4a4a60",
body_background_fill="#0f1419",
body_background_fill_dark="#0f1419",
body_text_color="#eef2f7",
body_text_color_dark="#eef2f7",
block_background_fill="#1a2332",
block_background_fill_dark="#1a2332",
block_border_color="#4b5563",
block_title_text_color="#ffffff",
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(
f"""
# ⚡ Trading Studio
**本地量化交易复盘 → 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})
""",
@@ -241,24 +418,12 @@ def build_app() -> gr.Blocks:
)
with gr.Row():
ollama_status = gr.Textbox(
label="Ollama 节点",
value="检测中...",
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)
ollama_status = gr.HTML(value=_status_html("Ollama 节点", "正在检测...", "warn"))
speaker_status = gr.HTML(value=_status_html("音色状态", "正在检测...", "warn"))
refresh_btn = gr.Button("🔄 刷新状态", variant="secondary", scale=0)
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],
)
@@ -360,7 +525,7 @@ def build_app() -> gr.Blocks:
)
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],
)