LifeFlow-AI / ui /theme.py
Marco310's picture
rebuild app.py
4abc17c
raw
history blame
17.3 kB
"""
LifeFlow AI - Theme System
提供完整的主題和樣式管理,支援動態主題切換
"""
def get_enhanced_css() -> str:
"""
改進的 CSS 系統:
1. ✅ 修復色調功能 - 確保主題顏色正確應用
2. ✅ 完整支援深淺模式切換
3. ✅ 修復 Exit 按鈕樣式(Step 2)
4. ✅ 調整 Setting 和 Doc 按鈕位置和大小
"""
return """
<style>
:root {
/* 品牌色 - 固定不變,保持品牌識別度 */
--primary-color: #4A90E2;
--secondary-color: #50C878;
--accent-color: #F5A623;
--primary-glow: rgba(74, 144, 226, 0.3);
/* 功能色 */
--success-color: #7ED321;
--warning-color: #F5A623;
--danger-color: #FF6B6B;
}
/* ============= 動畫效果 ============= */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes breathing {
0%, 100% { box-shadow: 0 0 10px var(--agent-glow, rgba(74, 144, 226, 0.3)); }
50% { box-shadow: 0 0 25px var(--agent-glow, rgba(74, 144, 226, 0.6)), 0 0 40px var(--agent-glow, rgba(74, 144, 226, 0.3)); }
}
@keyframes slide-in-left {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
/* ============= 全域設定 ============= */
.gradio-container {
background: var(--background-fill-primary) !important;
}
/* ============= 🔧 修復:Header 標題 ============= */
.app-header {
text-align: center;
padding: 40px 20px 30px 20px;
position: relative;
}
.app-header h1 {
font-size: 64px !important;
margin: 0 !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700 !important;
}
.app-header p {
font-size: 20px !important;
color: var(--body-text-color);
opacity: 0.8;
margin-top: 10px !important;
}
/* ============= 🔧 修復:主題切換按鈕 ============= */
#theme-toggle {
position: fixed !important;
top: 20px !important;
right: 110px !important;
z-index: 1000 !important;
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
max-width: 40px !important;
padding: 0 !important;
font-size: 18px !important;
border-radius: 10px !important;
background: var(--background-fill-secondary) !important;
border: 1.5px solid var(--border-color-primary) !important;
box-shadow: 0 2px 6px rgba(0,0,0,0.1) !important;
transition: all 0.3s ease !important;
cursor: pointer !important;
}
#theme-toggle:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 10px rgba(245, 166, 35, 0.3) !important;
background: var(--accent-color) !important;
color: white !important;
}
/* ============= 🔧 修復:Settings 按鈕 ============= */
#settings-btn {
position: fixed !important;
top: 20px !important;
right: 60px !important;
z-index: 1000 !important;
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
max-width: 40px !important;
padding: 0 !important;
font-size: 18px !important;
border-radius: 10px !important;
background: var(--background-fill-secondary) !important;
border: 1.5px solid var(--border-color-primary) !important;
box-shadow: 0 2px 6px rgba(0,0,0,0.1) !important;
transition: all 0.3s ease !important;
cursor: pointer !important;
}
#settings-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 10px rgba(74, 144, 226, 0.3) !important;
background: var(--primary-color) !important;
color: white !important;
}
/* ============= 🔧 修復:Doc 按鈕 ============= */
#doc-btn {
position: fixed !important;
top: 20px !important;
right: 10px !important;
z-index: 1000 !important;
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
max-width: 40px !important;
padding: 0 !important;
font-size: 18px !important;
border-radius: 10px !important;
background: var(--background-fill-secondary) !important;
border: 1.5px solid var(--border-color-primary) !important;
box-shadow: 0 2px 6px rgba(0,0,0,0.1) !important;
transition: all 0.3s ease !important;
cursor: pointer !important;
}
#doc-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 10px rgba(74, 144, 226, 0.3) !important;
background: var(--primary-color) !important;
color: white !important;
}
/* ============= 🔧 修復:Exit 按鈕樣式 (Step 2 - 頂部獨立) ============= */
#exit-button {
width: 100% !important;
height: 50px !important;
font-size: 16px !important;
font-weight: 600 !important;
border-radius: 10px !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border: none !important;
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.3) !important;
transition: all 0.3s ease !important;
margin-bottom: 15px !important;
cursor: pointer;
}
#exit-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.5) !important;
}
/* ============= 🔧 新增:Exit 按鈕樣式 (內聯版本 - 與 Ready to plan 並排) ============= */
#exit-button-inline {
height: 50px !important;
font-size: 15px !important;
font-weight: 600 !important;
border-radius: 10px !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border: none !important;
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.3) !important;
transition: all 0.3s ease !important;
cursor: pointer;
}
#exit-button-inline:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.5) !important;
}
/* ============= 串流輸出框 ============= */
.stream-container {
background: var(--background-fill-secondary);
border-radius: 10px;
padding: 15px;
min-height: 150px;
max-height: 300px;
overflow-y: auto;
margin: 15px 0;
border: 1px solid var(--border-color-primary);
font-family: 'Monaco', 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
}
.stream-text {
color: var(--body-text-color);
white-space: pre-wrap;
word-wrap: break-word;
}
.stream-cursor {
display: inline-block;
width: 8px;
height: 16px;
background: var(--primary-color);
animation: blink 1s infinite;
margin-left: 2px;
}
/* ============= Agent 卡片 ============= */
.agent-card {
background: var(--background-fill-secondary);
border-radius: 12px;
padding: 15px;
margin: 10px 0;
border-left: 4px solid var(--agent-color, var(--primary-color));
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.agent-card:hover {
transform: translateX(5px);
box-shadow: 0 4px 12px var(--agent-glow, rgba(74, 144, 226, 0.2));
}
.agent-card.active {
animation: breathing 2s infinite;
border-left-width: 6px;
}
.agent-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.agent-avatar {
font-size: 28px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}
.agent-info h3 {
margin: 0;
font-size: 16px;
color: var(--body-text-color);
}
.agent-role {
font-size: 12px;
color: var(--body-text-color);
opacity: 0.7;
}
.agent-status {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
margin-top: 8px;
}
.status-idle {
background: rgba(128, 128, 128, 0.2);
color: var(--body-text-color);
}
.status-working {
background: var(--primary-glow);
color: var(--primary-color);
animation: pulse 1.5s infinite;
}
.status-complete {
background: rgba(126, 211, 33, 0.2);
color: var(--success-color);
}
.agent-message {
margin-top: 8px;
padding: 8px;
background: var(--background-fill-primary);
border-radius: 6px;
font-size: 13px;
color: var(--body-text-color);
opacity: 0.9;
}
/* ============= Task 卡片 ============= */
.task-card {
background: var(--background-fill-secondary);
border-radius: 10px;
padding: 16px;
margin: 12px 0;
border-left: 4px solid var(--task-priority-color, #999);
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}
.task-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.task-title {
font-size: 16px;
font-weight: 600;
color: var(--body-text-color);
display: flex;
align-items: center;
gap: 10px;
}
.task-icon {
font-size: 24px;
}
.priority-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
}
.priority-high {
background: rgba(255, 107, 107, 0.2);
color: #FF6B6B;
}
.priority-medium {
background: rgba(245, 166, 35, 0.2);
color: #F5A623;
}
.priority-low {
background: rgba(126, 211, 33, 0.2);
color: #7ED321;
}
.task-details {
display: flex;
gap: 20px;
font-size: 13px;
color: var(--body-text-color);
opacity: 0.8;
margin-top: 10px;
}
.task-detail-item {
display: flex;
align-items: center;
gap: 6px;
}
/* ============= Summary Card ============= */
.summary-card {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
border-radius: 12px;
padding: 20px;
color: white;
margin: 15px 0;
box-shadow: 0 4px 15px rgba(74, 144, 226, 0.3);
}
.summary-stats {
display: flex;
justify-content: space-around;
margin-top: 15px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 28px;
font-weight: bold;
display: block;
}
.stat-label {
font-size: 12px;
opacity: 0.9;
margin-top: 5px;
}
/* ============= 🔧 優化:Ready to Plan 按鈕樣式(內聯佈局) ============= */
.ready-plan-button {
height: 50px !important;
font-size: 16px !important;
font-weight: 700 !important;
border-radius: 10px !important;
background: linear-gradient(135deg, #50C878 0%, #7ED321 100%) !important;
color: white !important;
border: none !important;
box-shadow: 0 3px 12px rgba(80, 200, 120, 0.4) !important;
transition: all 0.3s ease !important;
cursor: pointer;
}
.ready-plan-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(80, 200, 120, 0.6) !important;
background: linear-gradient(135deg, #5DD68D 0%, #8FE63F 100%) !important;
}
.ready-plan-button:active {
transform: translateY(0);
}
/* ============= Reasoning Timeline ============= */
.reasoning-timeline {
padding: 20px 0;
}
.reasoning-item {
padding: 15px;
margin: 10px 0;
border-radius: 8px;
background: var(--background-fill-secondary);
border-left: 3px solid var(--agent-color, var(--primary-color));
animation: fade-in 0.5s ease;
}
.reasoning-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.reasoning-agent {
font-weight: 600;
color: var(--agent-color, var(--primary-color));
}
.reasoning-time {
font-size: 11px;
opacity: 0.6;
margin-left: auto;
}
.reasoning-content {
color: var(--body-text-color);
opacity: 0.9;
line-height: 1.6;
}
/* ============= Modal 樣式 ============= */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--background-fill-primary);
border-radius: 16px;
padding: 30px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}
/* ============= Responsive ============= */
@media (max-width: 768px) {
#top-controls {
top: 10px;
right: 10px;
}
#top-controls button {
width: 40px !important;
height: 40px !important;
font-size: 18px !important;
}
.agent-card {
padding: 12px;
}
.task-card {
padding: 12px;
}
}
/* ============= 滾動條美化 ============= */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--background-fill-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--secondary-color);
}
/* ============= 🔧 主題切換 - 深色模式樣式 ============= */
.theme-dark {
background: #1a1a1a !important;
}
.theme-dark .gradio-container {
background: #1a1a1a !important;
}
.theme-dark [data-testid="block"] {
background: #2d2d2d !important;
}
.theme-dark .gr-box,
.theme-dark .gr-form,
.theme-dark .gr-input,
.theme-dark .gr-group {
background: #2d2d2d !important;
border-color: #444444 !important;
}
.theme-dark p,
.theme-dark label,
.theme-dark span,
.theme-dark h1,
.theme-dark h2,
.theme-dark h3 {
color: #e0e0e0 !important;
}
.theme-dark input,
.theme-dark textarea {
background: #3a3a3a !important;
color: #e0e0e0 !important;
border-color: #555555 !important;
}
.theme-dark button {
background: #3a3a3a !important;
color: #e0e0e0 !important;
border-color: #555555 !important;
}
/* 保持品牌色按鈕的顏色 */
.theme-dark #exit-button,
.theme-dark #exit-button-inline,
.theme-dark .ready-plan-button,
.theme-dark #theme-toggle,
.theme-dark #settings-btn,
.theme-dark #doc-btn {
/* 保持原有的漸變色 */
filter: brightness(0.9);
}
</style>
<script>
// 主題切換功能 - 頁面載入時執行
(function() {
// 等待 Gradio 完全載入
function initTheme() {
const savedTheme = localStorage.getItem('lifeflow-theme');
console.log('Initializing theme, saved:', savedTheme);
if (savedTheme === 'dark') {
const container = document.querySelector('.gradio-container');
if (container) {
container.classList.add('theme-dark');
document.body.classList.add('theme-dark');
console.log('Dark theme applied on load');
} else {
// 如果還沒找到容器,稍後再試
setTimeout(initTheme, 100);
}
}
}
// 頁面載入完成後執行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTheme);
} else {
initTheme();
}
// 也在 window load 時再次檢查
window.addEventListener('load', initTheme);
})();
</script>
"""