From e11caa59ab2551e14dd81e7a31c7cd24cc459eb5 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 12 Jun 2026 14:20:05 +0800 Subject: [PATCH] Improve UI contrast: high-visibility theme and status cards. Co-authored-by: Cursor --- app.py | 259 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 212 insertions(+), 47 deletions(-) diff --git a/app.py b/app.py index 14672e8..0e04348 100644 --- a/app.py +++ b/app.py @@ -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'
' + f'
{icon} {title}
' + f'
{clean}
' + f"
" + ) + + +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], )