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。""" """【音色锁定】从参考人声提取并保存 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],
) )