Spaces:
Sleeping
Sleeping
YeCanming
commited on
Commit
·
f2c0e49
1
Parent(s):
f598a7d
feat: merge
Browse files- .gitattributes +1 -0
- .streamlit/config.toml +16 -5
- .streamlit/theme.toml +2 -0
- .streamlit/themes/antropic.toml +20 -0
- .streamlit/themes/flowers_bright.toml +20 -0
- .streamlit/themes/spotify.toml +19 -0
- .streamlit/themes/willows_dark.toml +20 -0
- logo.png +3 -0
- src/streamlit_app.py +116 -6
- src/theme_selector.py +153 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
.streamlit/config.toml
CHANGED
|
@@ -1,6 +1,17 @@
|
|
| 1 |
[theme]
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
[theme]
|
| 2 |
+
base = "light"
|
| 3 |
+
baseFontSize = 15
|
| 4 |
+
primaryColor = "#FF5F7E"
|
| 5 |
+
backgroundColor = "#F9FAFB"
|
| 6 |
+
secondaryBackgroundColor = "#F0F4F8"
|
| 7 |
+
textColor = "#1F2937"
|
| 8 |
+
linkColor = "#2563EB"
|
| 9 |
+
borderColor = "#D1D5DB"
|
| 10 |
+
showWidgetBorder = false
|
| 11 |
+
baseRadius = "0.3rem"
|
| 12 |
+
font = "Poppins"
|
| 13 |
+
|
| 14 |
+
[theme.sidebar]
|
| 15 |
+
backgroundColor = "#FFFFFF"
|
| 16 |
+
secondaryBackgroundColor = "#F3F4F6"
|
| 17 |
+
borderColor = "#D1D5DB"
|
.streamlit/theme.toml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
theme_name = "花明 (Flowers Bright) 🌸"
|
| 2 |
+
theme_poem = "🌸「浅色但不苍白,明亮而不过曝,柔和中有力量」"
|
.streamlit/themes/antropic.toml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
theme_name = "Antropic"
|
| 2 |
+
|
| 3 |
+
[theme]
|
| 4 |
+
primaryColor = "#bb5a38"
|
| 5 |
+
backgroundColor = "#f4f3ed"
|
| 6 |
+
secondaryBackgroundColor = "#ecebe3"
|
| 7 |
+
textColor = "#3d3a2a"
|
| 8 |
+
linkColor = "#3d3a2a"
|
| 9 |
+
borderColor = "#d3d2ca"
|
| 10 |
+
showWidgetBorder = true
|
| 11 |
+
baseRadius = "0.6rem"
|
| 12 |
+
font = "SpaceGrotesk"
|
| 13 |
+
headingFont = "SpaceGroteskHeader"
|
| 14 |
+
codeFont = "SpaceMono"
|
| 15 |
+
codeBackgroundColor = "#ecebe4"
|
| 16 |
+
showSidebarBorder = true
|
| 17 |
+
|
| 18 |
+
[theme.sidebar]
|
| 19 |
+
backgroundColor = "#e8e7dd"
|
| 20 |
+
secondaryBackgroundColor = "#ecebe3"
|
.streamlit/themes/flowers_bright.toml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
theme_name = "花明 (Flowers Bright) 🌸"
|
| 2 |
+
theme_poem = "🌸「浅色但不苍白,明亮而不过曝,柔和中有力量」"
|
| 3 |
+
|
| 4 |
+
[theme]
|
| 5 |
+
base = "light"
|
| 6 |
+
baseFontSize = 15
|
| 7 |
+
primaryColor = "#FF5F7E" # 樱花粉
|
| 8 |
+
backgroundColor = "#F9FAFB" # 轻柔灰白
|
| 9 |
+
secondaryBackgroundColor = "#F0F4F8" # 卡片淡蓝灰
|
| 10 |
+
textColor = "#1F2937" # 蓝黑灰
|
| 11 |
+
linkColor = "#2563EB" # 浅蓝色链接
|
| 12 |
+
borderColor = "#D1D5DB" # 卡片分界线
|
| 13 |
+
showWidgetBorder = false
|
| 14 |
+
baseRadius = "0.3rem"
|
| 15 |
+
font = "Poppins" # 保持现代圆润感
|
| 16 |
+
|
| 17 |
+
[theme.sidebar]
|
| 18 |
+
backgroundColor = "#FFFFFF"
|
| 19 |
+
secondaryBackgroundColor = "#F3F4F6"
|
| 20 |
+
borderColor = "#D1D5DB"
|
.streamlit/themes/spotify.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
theme_name = "Spotify"
|
| 2 |
+
|
| 3 |
+
[theme]
|
| 4 |
+
base = "dark"
|
| 5 |
+
baseFontSize = 15
|
| 6 |
+
primaryColor = "#1ED760"
|
| 7 |
+
backgroundColor = "#121212"
|
| 8 |
+
secondaryBackgroundColor = "#333333"
|
| 9 |
+
textColor = "#FFFFFF"
|
| 10 |
+
linkColor = "#9D9D9D"
|
| 11 |
+
borderColor = "#7F7F7F"
|
| 12 |
+
showWidgetBorder = false
|
| 13 |
+
baseRadius = "0.3rem"
|
| 14 |
+
font = "Poppins"
|
| 15 |
+
|
| 16 |
+
[theme.sidebar]
|
| 17 |
+
backgroundColor = "#000000"
|
| 18 |
+
secondaryBackgroundColor = "#333333"
|
| 19 |
+
borderColor = "#696969"
|
.streamlit/themes/willows_dark.toml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
theme_name = "柳暗 (Willows Dark) 🌒"
|
| 2 |
+
theme_poem = "🌒「深而不死黑,蓝而不夺目,静而不沉闷」柳影婆娑之下,代码悄然生长。"
|
| 3 |
+
|
| 4 |
+
[theme]
|
| 5 |
+
base = "dark"
|
| 6 |
+
baseFontSize = 15
|
| 7 |
+
primaryColor = "#6EA8FE" # 柔和的蓝色光感
|
| 8 |
+
backgroundColor = "#0D1117" # 深夜蓝黑
|
| 9 |
+
secondaryBackgroundColor = "#1A1F2B" # 柔和灰蓝
|
| 10 |
+
textColor = "#D1D5DB" # 清晰柔白
|
| 11 |
+
linkColor = "#B8C0FF" # 柔紫蓝
|
| 12 |
+
borderColor = "#2E3440" # 北极灰边
|
| 13 |
+
showWidgetBorder = false
|
| 14 |
+
baseRadius = "0.3rem"
|
| 15 |
+
font = "JetBrains Mono" # 强科技感
|
| 16 |
+
|
| 17 |
+
[theme.sidebar]
|
| 18 |
+
backgroundColor = "#0A0A0A"
|
| 19 |
+
secondaryBackgroundColor = "#1A1A1A"
|
| 20 |
+
borderColor = "#2E3440"
|
logo.png
ADDED
|
Git LFS Details
|
src/streamlit_app.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
| 1 |
import streamlit as st
|
|
|
|
|
|
|
|
|
|
| 2 |
from pathlib import Path
|
| 3 |
import plotly.graph_objects as go
|
| 4 |
import plotly.express as px
|
|
@@ -6,14 +9,18 @@ from plotly.subplots import make_subplots
|
|
| 6 |
import pandas as pd
|
| 7 |
import time
|
| 8 |
|
|
|
|
|
|
|
|
|
|
| 9 |
# 导入重构后的模块
|
| 10 |
try:
|
| 11 |
from utils import DATA_ROOT_PATH, AppMode
|
| 12 |
from data_models import Study, Trial # Study, Trial will be used
|
| 13 |
from data_loader import discover_studies_cached, ensure_data_directory_exists
|
|
|
|
| 14 |
except ImportError as e:
|
| 15 |
st.error(
|
| 16 |
-
f"导入模块失败,请确保 utils.py, data_models.py, data_loader.py 文件存在于正确的位置: {e}"
|
| 17 |
)
|
| 18 |
st.stop()
|
| 19 |
|
|
@@ -40,14 +47,13 @@ if "auto_play_speed" not in st.session_state:
|
|
| 40 |
if "auto_play_needs_rerun" not in st.session_state:
|
| 41 |
st.session_state.auto_play_needs_rerun = False
|
| 42 |
|
| 43 |
-
|
| 44 |
-
st.set_page_config(layout="wide", page_title="柳暗花明 (flowillower)")
|
| 45 |
|
| 46 |
|
| 47 |
# --- UI Rendering ---
|
| 48 |
|
| 49 |
# --- Header ---
|
| 50 |
-
header_cols = st.columns([2, 3, 1.5, 0.5, 0.5, 0.5])
|
| 51 |
with header_cols[0]:
|
| 52 |
st.markdown("## 柳暗花明")
|
| 53 |
st.caption("flowillower")
|
|
@@ -87,6 +93,10 @@ else:
|
|
| 87 |
with header_cols[3]: st.button("➕", help="添加 (Add)", disabled=True)
|
| 88 |
with header_cols[4]: st.button("⚙️", help="设置 (Settings)", disabled=True)
|
| 89 |
with header_cols[5]: st.button("👤", help="用户 (User)", disabled=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
st.markdown("---")
|
| 91 |
|
| 92 |
# --- Sidebar ---
|
|
@@ -175,7 +185,8 @@ if current_study and current_trial:
|
|
| 175 |
with control_cols[0]:
|
| 176 |
# 滑动条
|
| 177 |
if st.session_state.shared_selected_global_step is None:
|
| 178 |
-
|
|
|
|
| 179 |
|
| 180 |
# 确保当前选中的步骤在有效范围内
|
| 181 |
if st.session_state.shared_selected_global_step not in all_global_steps:
|
|
@@ -273,6 +284,105 @@ if current_study and current_trial:
|
|
| 273 |
|
| 274 |
with st.container(border=True):
|
| 275 |
st.subheader(metric_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
try:
|
| 277 |
# 创建 Plotly 图表
|
| 278 |
fig = go.Figure()
|
|
@@ -358,7 +468,7 @@ if current_study and current_trial:
|
|
| 358 |
|
| 359 |
except Exception as e:
|
| 360 |
st.error(f"为指标 '{metric_name}' 生成图表时出错: {e}")
|
| 361 |
-
|
| 362 |
# raise e
|
| 363 |
|
| 364 |
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
+
# --- Page Configuration ---
|
| 3 |
+
st.set_page_config(layout="wide", page_title="柳暗花明 (flowillower)", page_icon=":sunrise_over_mountains:", initial_sidebar_state="expanded")
|
| 4 |
+
|
| 5 |
from pathlib import Path
|
| 6 |
import plotly.graph_objects as go
|
| 7 |
import plotly.express as px
|
|
|
|
| 9 |
import pandas as pd
|
| 10 |
import time
|
| 11 |
|
| 12 |
+
# --- Logo ---
|
| 13 |
+
st.logo("logo.png", icon_image="logo.png")
|
| 14 |
+
|
| 15 |
# 导入重构后的模块
|
| 16 |
try:
|
| 17 |
from utils import DATA_ROOT_PATH, AppMode
|
| 18 |
from data_models import Study, Trial # Study, Trial will be used
|
| 19 |
from data_loader import discover_studies_cached, ensure_data_directory_exists
|
| 20 |
+
from theme_selector import render_theme_selector # 新增:导入主题选择器
|
| 21 |
except ImportError as e:
|
| 22 |
st.error(
|
| 23 |
+
f"导入模块失败,请确保 utils.py, data_models.py, data_loader.py, theme_selector.py 文件存在于正确的位置: {e}"
|
| 24 |
)
|
| 25 |
st.stop()
|
| 26 |
|
|
|
|
| 47 |
if "auto_play_needs_rerun" not in st.session_state:
|
| 48 |
st.session_state.auto_play_needs_rerun = False
|
| 49 |
|
| 50 |
+
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
# --- UI Rendering ---
|
| 54 |
|
| 55 |
# --- Header ---
|
| 56 |
+
header_cols = st.columns([2, 3, 1.5, 0.5, 0.5, 0.5, 1]) # 新增一列用于主题选择器
|
| 57 |
with header_cols[0]:
|
| 58 |
st.markdown("## 柳暗花明")
|
| 59 |
st.caption("flowillower")
|
|
|
|
| 93 |
with header_cols[3]: st.button("➕", help="添加 (Add)", disabled=True)
|
| 94 |
with header_cols[4]: st.button("⚙️", help="设置 (Settings)", disabled=True)
|
| 95 |
with header_cols[5]: st.button("👤", help="用户 (User)", disabled=True)
|
| 96 |
+
with header_cols[6]: # 新增:主题选择器列
|
| 97 |
+
with st.container():
|
| 98 |
+
# st.markdown("**主题**")
|
| 99 |
+
render_theme_selector()
|
| 100 |
st.markdown("---")
|
| 101 |
|
| 102 |
# --- Sidebar ---
|
|
|
|
| 185 |
with control_cols[0]:
|
| 186 |
# 滑动条
|
| 187 |
if st.session_state.shared_selected_global_step is None:
|
| 188 |
+
# 默认选择最后一个step
|
| 189 |
+
st.session_state.shared_selected_global_step = max_step
|
| 190 |
|
| 191 |
# 确保当前选中的步骤在有效范围内
|
| 192 |
if st.session_state.shared_selected_global_step not in all_global_steps:
|
|
|
|
| 284 |
|
| 285 |
with st.container(border=True):
|
| 286 |
st.subheader(metric_name)
|
| 287 |
+
|
| 288 |
+
# 添加metric组件 - 显示当前值和增量
|
| 289 |
+
try:
|
| 290 |
+
current_step = st.session_state.shared_selected_global_step
|
| 291 |
+
|
| 292 |
+
# 获取所有可能的track
|
| 293 |
+
all_tracks = df_metric['track'].unique() if 'track' in df_metric.columns else [None]
|
| 294 |
+
|
| 295 |
+
# 为每个track创建metric组件
|
| 296 |
+
if len(all_tracks) > 1:
|
| 297 |
+
metric_cols = st.columns(len(all_tracks))
|
| 298 |
+
else:
|
| 299 |
+
metric_cols = [st] # 使用整个容器
|
| 300 |
+
|
| 301 |
+
for idx, track in enumerate(all_tracks):
|
| 302 |
+
# 查找当前步骤的数据
|
| 303 |
+
if track is not None:
|
| 304 |
+
current_step_data = df_metric[
|
| 305 |
+
(df_metric['global_step'] == current_step) &
|
| 306 |
+
(df_metric['track'] == track)
|
| 307 |
+
]
|
| 308 |
+
else:
|
| 309 |
+
current_step_data = df_metric[df_metric['global_step'] == current_step]
|
| 310 |
+
|
| 311 |
+
current_value = None
|
| 312 |
+
delta_value = None
|
| 313 |
+
|
| 314 |
+
# 如果当前步骤没有该track的数据,向前查找最近的步骤
|
| 315 |
+
if current_step_data.empty:
|
| 316 |
+
# 向前查找最近的有该track数据的步骤
|
| 317 |
+
current_index = all_global_steps.index(current_step)
|
| 318 |
+
for search_idx in range(current_index - 1, -1, -1):
|
| 319 |
+
search_step = all_global_steps[search_idx]
|
| 320 |
+
if track is not None:
|
| 321 |
+
search_data = df_metric[
|
| 322 |
+
(df_metric['global_step'] == search_step) &
|
| 323 |
+
(df_metric['track'] == track)
|
| 324 |
+
]
|
| 325 |
+
else:
|
| 326 |
+
search_data = df_metric[df_metric['global_step'] == search_step]
|
| 327 |
+
|
| 328 |
+
if not search_data.empty:
|
| 329 |
+
current_value = search_data['value'].iloc[0]
|
| 330 |
+
current_step_found = search_step
|
| 331 |
+
break
|
| 332 |
+
else:
|
| 333 |
+
current_value = current_step_data['value'].iloc[0]
|
| 334 |
+
current_step_found = current_step
|
| 335 |
+
|
| 336 |
+
# 计算增量:查找比当前找到的步骤更早的数据
|
| 337 |
+
if current_value is not None:
|
| 338 |
+
current_found_index = all_global_steps.index(current_step_found)
|
| 339 |
+
for prev_idx in range(current_found_index - 1, -1, -1):
|
| 340 |
+
prev_step = all_global_steps[prev_idx]
|
| 341 |
+
if track is not None:
|
| 342 |
+
prev_step_data = df_metric[
|
| 343 |
+
(df_metric['global_step'] == prev_step) &
|
| 344 |
+
(df_metric['track'] == track)
|
| 345 |
+
]
|
| 346 |
+
else:
|
| 347 |
+
prev_step_data = df_metric[df_metric['global_step'] == prev_step]
|
| 348 |
+
|
| 349 |
+
if not prev_step_data.empty:
|
| 350 |
+
prev_value = prev_step_data['value'].iloc[0]
|
| 351 |
+
delta_value = current_value - prev_value
|
| 352 |
+
break
|
| 353 |
+
|
| 354 |
+
# 显示metric组件
|
| 355 |
+
with metric_cols[idx] if len(all_tracks) > 1 else metric_cols[0]:
|
| 356 |
+
if current_value is not None:
|
| 357 |
+
# 确定label
|
| 358 |
+
if track is not None:
|
| 359 |
+
if current_step_found != current_step:
|
| 360 |
+
label = f"{track} (Step {current_step_found})"
|
| 361 |
+
else:
|
| 362 |
+
label = f"{track}"
|
| 363 |
+
else:
|
| 364 |
+
if current_step_found != current_step:
|
| 365 |
+
label = f"当前值 (Step {current_step_found})"
|
| 366 |
+
else:
|
| 367 |
+
label = f"当前值 (Step {current_step})"
|
| 368 |
+
|
| 369 |
+
st.metric(
|
| 370 |
+
label=label,
|
| 371 |
+
value=f"{current_value:.4f}",
|
| 372 |
+
delta=f"{delta_value:.4f}" if delta_value is not None else None
|
| 373 |
+
)
|
| 374 |
+
else:
|
| 375 |
+
# 没有找到任何数据
|
| 376 |
+
track_label = track if track is not None else "数据"
|
| 377 |
+
st.metric(
|
| 378 |
+
label=f"{track_label}",
|
| 379 |
+
value="无数据",
|
| 380 |
+
delta=None
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
except Exception as e:
|
| 384 |
+
st.warning(f"计算指标值时出错: {e}")
|
| 385 |
+
|
| 386 |
try:
|
| 387 |
# 创建 Plotly 图表
|
| 388 |
fig = go.Figure()
|
|
|
|
| 468 |
|
| 469 |
except Exception as e:
|
| 470 |
st.error(f"为指标 '{metric_name}' 生成图表时出错: {e}")
|
| 471 |
+
st.dataframe(df_metric)
|
| 472 |
# raise e
|
| 473 |
|
| 474 |
|
src/theme_selector.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import toml
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import os
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
class ThemeSelector:
|
| 8 |
+
def __init__(self, themes_dir=".streamlit/themes", config_path=".streamlit/config.toml"):
|
| 9 |
+
self.themes_dir = Path(themes_dir)
|
| 10 |
+
self.config_path = Path(config_path)
|
| 11 |
+
self.themes = {}
|
| 12 |
+
self.load_themes()
|
| 13 |
+
|
| 14 |
+
def load_themes(self):
|
| 15 |
+
"""加载所有主题文件"""
|
| 16 |
+
self.themes = {}
|
| 17 |
+
if not self.themes_dir.exists():
|
| 18 |
+
return
|
| 19 |
+
|
| 20 |
+
for theme_file in self.themes_dir.glob("*.toml"):
|
| 21 |
+
try:
|
| 22 |
+
theme_data = toml.load(theme_file)
|
| 23 |
+
|
| 24 |
+
# 从根级别获取theme_name和theme_poem
|
| 25 |
+
theme_name = theme_data.get("theme_name", theme_file.stem)
|
| 26 |
+
theme_poem = theme_data.get("theme_poem", "")
|
| 27 |
+
theme_config = theme_data.get("theme", {})
|
| 28 |
+
|
| 29 |
+
self.themes[theme_name] = {
|
| 30 |
+
"file": theme_file,
|
| 31 |
+
"name": theme_name,
|
| 32 |
+
"poem": theme_poem,
|
| 33 |
+
"config": theme_config
|
| 34 |
+
}
|
| 35 |
+
except Exception as e:
|
| 36 |
+
st.warning(f"读取主题文件 {theme_file} 失败: {e}")
|
| 37 |
+
|
| 38 |
+
def get_current_theme(self):
|
| 39 |
+
"""获取当前主题名称"""
|
| 40 |
+
if not self.config_path.exists():
|
| 41 |
+
return None
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
# config = toml.load(self.config_path)
|
| 45 |
+
# # 从根级别读取theme_name
|
| 46 |
+
# current_theme_name = config.get("theme") or {}
|
| 47 |
+
# current_theme_name = current_theme_name.get("theme_name")
|
| 48 |
+
# return current_theme_name
|
| 49 |
+
theme_toml = self.config_path.parent/"theme.toml"
|
| 50 |
+
theme = toml.load(theme_toml)
|
| 51 |
+
return theme.get("theme_name")
|
| 52 |
+
|
| 53 |
+
except Exception:
|
| 54 |
+
return None
|
| 55 |
+
|
| 56 |
+
def apply_theme(self, theme_name):
|
| 57 |
+
"""应用选定的主题"""
|
| 58 |
+
if theme_name not in self.themes:
|
| 59 |
+
st.error(f"主题 '{theme_name}' 不存在")
|
| 60 |
+
return False
|
| 61 |
+
|
| 62 |
+
try:
|
| 63 |
+
# 确保配置目录存在
|
| 64 |
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
| 65 |
+
|
| 66 |
+
# 读取现有配置或创建新配置
|
| 67 |
+
config = {}
|
| 68 |
+
if self.config_path.exists():
|
| 69 |
+
try:
|
| 70 |
+
config = toml.load(self.config_path)
|
| 71 |
+
except Exception:
|
| 72 |
+
config = {}
|
| 73 |
+
|
| 74 |
+
# 添加根级别的theme_name和theme_poem
|
| 75 |
+
# config["theme_name"] = self.themes[theme_name]["name"]
|
| 76 |
+
# config["theme_poem"] = self.themes[theme_name]["poem"]
|
| 77 |
+
|
| 78 |
+
# 更新主题配置
|
| 79 |
+
theme_config = self.themes[theme_name]["config"].copy()
|
| 80 |
+
# theme_config["theme_name"] = self.themes[theme_name]["name"]
|
| 81 |
+
# theme_config["theme_poem"] = self.themes[theme_name]["poem"]
|
| 82 |
+
config["theme"] |= theme_config
|
| 83 |
+
|
| 84 |
+
# 写入配置文件
|
| 85 |
+
with open(self.config_path, "w", encoding="utf-8") as f:
|
| 86 |
+
toml.dump(config, f)
|
| 87 |
+
|
| 88 |
+
theme_toml = self.config_path.parent/"theme.toml"
|
| 89 |
+
with open(theme_toml, "w", encoding="utf-8") as f:
|
| 90 |
+
toml.dump(dict(
|
| 91 |
+
theme_name = self.themes[theme_name]["name"],
|
| 92 |
+
theme_poem = self.themes[theme_name]["poem"]
|
| 93 |
+
), f)
|
| 94 |
+
|
| 95 |
+
return True
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
st.error(f"应用主题失败: {e}")
|
| 99 |
+
return False
|
| 100 |
+
|
| 101 |
+
def render_theme_selector(self):
|
| 102 |
+
"""渲染主题选择器UI"""
|
| 103 |
+
if not self.themes:
|
| 104 |
+
st.warning("未找到可用主题")
|
| 105 |
+
return
|
| 106 |
+
|
| 107 |
+
theme_names = list(self.themes.keys())
|
| 108 |
+
current_theme = self.get_current_theme()
|
| 109 |
+
|
| 110 |
+
# 确定当前选中的索引
|
| 111 |
+
current_index = 0
|
| 112 |
+
if current_theme and current_theme in theme_names:
|
| 113 |
+
current_index = theme_names.index(current_theme)
|
| 114 |
+
|
| 115 |
+
# 主题选择下拉菜单
|
| 116 |
+
selected_theme = st.selectbox(
|
| 117 |
+
"选择主题",
|
| 118 |
+
options=theme_names,
|
| 119 |
+
index=current_index,
|
| 120 |
+
format_func=lambda x: self.themes[x]["name"],
|
| 121 |
+
key="theme_selector_widget",
|
| 122 |
+
label_visibility="collapsed"
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
# 如果选择了新主题
|
| 126 |
+
if selected_theme != current_theme:
|
| 127 |
+
if self.apply_theme(selected_theme):
|
| 128 |
+
# 显示主题诗句
|
| 129 |
+
theme_poem = self.themes[selected_theme]["poem"]
|
| 130 |
+
if theme_poem:
|
| 131 |
+
st.toast(f"✨ {theme_poem}", icon="🎨")
|
| 132 |
+
else:
|
| 133 |
+
st.toast(f"已切换到主题: {selected_theme}", icon="🎨")
|
| 134 |
+
|
| 135 |
+
time.sleep(3)
|
| 136 |
+
# 延迟重新运行以应用主题
|
| 137 |
+
st.rerun()
|
| 138 |
+
|
| 139 |
+
return selected_theme
|
| 140 |
+
|
| 141 |
+
# 全局主题选择器实例
|
| 142 |
+
_theme_selector = None
|
| 143 |
+
|
| 144 |
+
def get_theme_selector():
|
| 145 |
+
"""获取全局主题选择器实例"""
|
| 146 |
+
global _theme_selector
|
| 147 |
+
if _theme_selector is None:
|
| 148 |
+
_theme_selector = ThemeSelector()
|
| 149 |
+
return _theme_selector
|
| 150 |
+
|
| 151 |
+
def render_theme_selector():
|
| 152 |
+
"""便捷函数:渲染主题选择器"""
|
| 153 |
+
return get_theme_selector().render_theme_selector()
|