Lychee-Rerank保姆级教程:添加自定义CSS美化Streamlit界面与导出按钮
1. 引言
如果你正在使用Lychee-Rerank这个本地检索相关性评分工具,可能会觉得它的界面虽然功能齐全,但看起来有点“朴素”。默认的Streamlit界面虽然实用,但在美观度和用户体验上还有提升空间。特别是当你需要频繁使用这个工具,或者想把它分享给团队成员时,一个更专业、更美观的界面会让人感觉更舒服。
今天我要分享的就是如何给Lychee-Rerank“化妆”——通过添加自定义CSS来美化界面,并且增加一个实用的导出按钮,让你能把评分结果保存下来。整个过程不需要你懂复杂的Web开发,只需要几行简单的代码就能实现。
学完这篇教程,你将能够:
- 理解Streamlit如何支持自定义样式
- 掌握添加自定义CSS的基本方法
- 为Lychee-Rerank界面添加美观的样式
- 实现结果导出功能(保存为CSV文件)
- 打造一个既美观又实用的本地评分工具
前置知识:
- 基本的Python语法
- 已经成功运行过Lychee-Rerank
- 会用文本编辑器修改代码文件
不用担心,即使你是前端小白,跟着步骤一步步来,也能轻松完成。我们主要修改的是app.py这个文件,所有的改动都很直观。
2. 为什么需要美化界面和导出功能?
2.1 默认界面的局限性
Lychee-Rerank默认的Streamlit界面确实能用,但有几个地方可以做得更好:
视觉体验方面:
- 界面元素比较紧凑,长时间使用容易视觉疲劳
- 颜色搭配比较基础,缺乏层次感
- 按钮和输入框的样式比较普通
功能体验方面:
- 评分结果只能在网页上查看,无法保存
- 每次需要结果时都要重新计算
- 无法将结果分享给其他人或导入其他工具
2.2 自定义CSS能带来什么?
通过添加自定义CSS,我们可以:
改善视觉效果
- 调整颜色方案,让界面更舒适
- 优化间距和布局,提高可读性
- 添加一些视觉反馈,提升交互体验
增强专业感
- 让工具看起来更像一个成熟的产品
- 统一的视觉风格提升可信度
- 更好的组织信息呈现
提升使用效率
- 清晰的视觉层次帮助快速定位信息
- 合理的颜色编码让结果一目了然
- 舒适的界面减少操作疲劳
2.3 导出功能的实际价值
添加导出按钮后,你可以:
- 保存历史记录:把重要的评分结果保存下来,方便后续查阅
- 数据分析:将结果导入Excel或其他分析工具进行进一步处理
- 分享协作:把结果文件发给同事或团队成员
- 批量处理:结合其他工具实现自动化工作流
3. 准备工作
3.1 确认文件结构
首先,确保你的Lychee-Rerank项目结构是这样的:
lychee-rerank/ ├── app.py # 主程序文件,我们要修改这个 ├── requirements.txt # 依赖包列表 ├── model/ # 模型文件目录 │ └── ... # 模型权重文件 └── ... # 其他配置文件如果你是通过CSDN星图镜像部署的,文件结构应该已经准备好了。如果还没有安装Lychee-Rerank,可以按照官方文档先完成基础部署。
3.2 备份原始文件
在开始修改之前,强烈建议先备份原始的app.py文件:
# 进入项目目录 cd lychee-rerank # 备份原始文件 cp app.py app.py.backup这样即使修改过程中出现问题,也能快速恢复。
3.3 了解Streamlit的样式注入方式
Streamlit提供了几种添加自定义样式的方法:
- 使用
st.markdown配合HTML/CSS:最灵活的方式,可以嵌入任意CSS代码 - 使用
st.html:直接插入HTML片段 - 使用主题配置:通过
config.toml文件配置主题
我们主要使用第一种方法,因为它最简单直接,不需要额外的配置文件。
4. 添加自定义CSS美化界面
4.1 找到合适的位置添加CSS
打开app.py文件,我们需要在Streamlit应用初始化后、主界面渲染前添加CSS代码。通常可以在文件开头部分,导入库之后添加。
找到类似这样的代码位置:
import streamlit as st import torch # ... 其他导入 # 在这里添加CSS代码4.2 完整的CSS美化代码
在合适的位置添加以下CSS代码:
# 添加自定义CSS美化界面 st.markdown(""" <style> /* 整体页面样式 */ .stApp { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } /* 主容器样式 */ .main { background-color: rgba(255, 255, 255, 0.95); border-radius: 20px; padding: 30px; margin: 20px auto; max-width: 1200px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); } /* 标题样式 */ h1 { color: #2d3748; text-align: center; margin-bottom: 30px; font-weight: 700; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; padding-bottom: 15px; border-bottom: 3px solid #e2e8f0; } /* 输入区域样式 */ .stTextArea textarea, .stTextInput input { border: 2px solid #e2e8f0; border-radius: 12px; padding: 15px; font-size: 16px; transition: all 0.3s ease; background-color: #f8fafc; } .stTextArea textarea:focus, .stTextInput input:focus { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); background-color: white; } /* 按钮样式 */ .stButton button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 14px 28px; border-radius: 12px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; width: 100%; margin-top: 10px; } .stButton button:hover { transform: translateY(-2px); box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3); } .stButton button:active { transform: translateY(0); } /* 导出按钮特殊样式 */ .export-btn { background: linear-gradient(135deg, #48bb78 0%, #38a169 100%) !important; } .export-btn:hover { box-shadow: 0 10px 25px rgba(72, 187, 120, 0.3) !important; } /* 结果区域样式 */ .result-card { background: white; border-radius: 15px; padding: 20px; margin: 15px 0; border-left: 5px solid; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease; } .result-card:hover { transform: translateX(5px); } /* 进度条样式 */ .stProgress > div > div > div { background: linear-gradient(90deg, #48bb78, #ecc94b, #f56565); border-radius: 10px; } /* 分数颜色样式 */ .high-score { color: #48bb78; font-weight: bold; } .medium-score { color: #ecc94b; font-weight: bold; } .low-score { color: #f56565; font-weight: bold; } /* 排名徽章样式 */ .rank-badge { display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; width: 40px; height: 40px; border-radius: 50%; text-align: center; line-height: 40px; font-weight: bold; margin-right: 15px; } /* 响应式调整 */ @media (max-width: 768px) { .main { margin: 10px; padding: 20px; } h1 { font-size: 24px; } } </style> """, unsafe_allow_html=True) # 添加主容器 st.markdown('<div class="main">', unsafe_allow_html=True)这段CSS代码做了以下几件事:
- 整体背景:添加了渐变色背景,让页面更有层次感
- 主容器:创建了一个圆角卡片容器,包含阴影和边框
- 标题美化:使用渐变色文字效果,更加醒目
- 输入框优化:添加了焦点状态和过渡动画
- 按钮美化:使用渐变色背景,添加悬停效果
- 结果卡片:为每个结果项添加卡片式设计
- 进度条美化:使用渐变色进度条
- 响应式设计:适配不同屏幕尺寸
4.3 在页面末尾关闭容器
在app.py文件的最后,添加关闭容器的代码:
# ... 原有的Streamlit代码 # 在文件末尾添加 st.markdown('</div>', unsafe_allow_html=True)这样就完成了基本的界面美化。现在运行应用,你会看到界面已经焕然一新了。
5. 添加导出按钮功能
5.1 理解导出需求
在添加导出按钮之前,我们需要明确要导出什么数据。对于Lychee-Rerank,最有价值的数据是:
- 查询语句(Query)
- 指令(Instruction)
- 评分结果:包括排名、分数、文档内容
- 时间戳:导出时间
最合适的格式是CSV(逗号分隔值),因为:
- 几乎所有数据处理工具都支持CSV
- 人类可读,可以直接用文本编辑器打开
- 结构简单,易于生成
5.2 修改评分结果处理逻辑
首先,我们需要修改结果处理部分,把结果保存到一个变量中,方便后续导出。找到计算评分结果的代码部分(通常在点击"计算相关性分数"按钮后的处理逻辑中)。
假设原有的代码结构是这样的:
if st.button("🚀 计算相关性分数"): # 获取输入 instruction = st.session_state.get("instruction", default_instruction) query = st.session_state.get("query", "") documents = st.session_state.get("documents", default_docs).split("\n") # 计算评分 results = [] progress_bar = st.progress(0) for i, doc in enumerate(documents): if doc.strip(): score = calculate_score(instruction, query, doc) results.append({ "document": doc, "score": score }) progress_bar.progress((i + 1) / len(documents)) # 排序结果 results.sort(key=lambda x: x["score"], reverse=True) # 显示结果 for i, result in enumerate(results): # 原有的显示逻辑...我们需要做两处修改:
- 将结果保存到session_state,这样导出按钮可以访问到
- 添加导出按钮和功能
5.3 完整的导出功能实现
下面是完整的修改示例:
import pandas as pd import datetime from io import StringIO # ... 原有的导入和CSS代码 ... # 在计算评分后,保存结果到session_state if st.button("🚀 计算相关性分数"): # 获取输入 instruction = st.session_state.get("instruction", default_instruction) query = st.session_state.get("query", "") documents = st.session_state.get("documents", default_docs).split("\n") # 计算评分 results = [] progress_bar = st.progress(0) for i, doc in enumerate(documents): if doc.strip(): score = calculate_score(instruction, query, doc) results.append({ "rank": i + 1, "score": float(score), "document": doc.strip(), "query": query, "instruction": instruction }) progress_bar.progress((i + 1) / len(documents)) # 按分数排序 results.sort(key=lambda x: x["score"], reverse=True) # 更新排名 for i, result in enumerate(results): result["rank"] = i + 1 # 保存到session_state,供导出使用 st.session_state["last_results"] = results st.session_state["last_query"] = query st.session_state["last_instruction"] = instruction # 显示结果 st.subheader("📊 评分结果") for result in results: score = result["score"] # 确定颜色类别 if score > 0.8: color_class = "high-score" color_hex = "#48bb78" label = "高相关性" elif score > 0.4: color_class = "medium-score" color_hex = "#ecc94b" label = "中相关性" else: color_class = "low-score" color_hex = "#f56565" label = "低相关性" # 使用HTML显示结果卡片 st.markdown(f""" <div class="result-card" style="border-left-color: {color_hex};"> <div style="display: flex; align-items: center; margin-bottom: 10px;"> <span class="rank-badge">#{result['rank']}</span> <div> <span style="font-size: 18px; font-weight: bold;">分数: <span class="{color_class}">{score:.6f}</span></span> <span style="margin-left: 15px; padding: 4px 12px; background-color: {color_hex}20; color: {color_hex}; border-radius: 12px; font-size: 14px;">{label}</span> </div> </div> <div style="margin-top: 10px;"> <div style="font-size: 14px; color: #718096; margin-bottom: 5px;">文档内容:</div> <div style="background-color: #f7fafc; padding: 12px; border-radius: 8px; border-left: 3px solid #cbd5e0;"> {result['document'][:200]}{'...' if len(result['document']) > 200 else ''} </div> </div> <div style="margin-top: 10px;"> <div style="width: 100%; background-color: #e2e8f0; height: 8px; border-radius: 4px; overflow: hidden;"> <div style="width: {score*100}%; height: 100%; background: linear-gradient(90deg, {color_hex}00, {color_hex});"></div> </div> </div> </div> """, unsafe_allow_html=True) # 添加导出按钮(在结果显示区域之后) if "last_results" in st.session_state and st.session_state["last_results"]: st.markdown("---") st.subheader("💾 导出结果") col1, col2 = st.columns([3, 1]) with col1: st.info("评分计算完成!您可以将结果导出为CSV文件,方便后续分析或分享。") with col2: # 创建导出功能 def export_to_csv(): results = st.session_state["last_results"] query = st.session_state["last_query"] instruction = st.session_state["last_instruction"] # 准备数据 export_data = [] for result in results: export_data.append({ "排名": result["rank"], "相关性分数": result["score"], "文档内容": result["document"], "查询语句": query, "评分指令": instruction, "导出时间": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # 创建DataFrame df = pd.DataFrame(export_data) # 转换为CSV csv_buffer = StringIO() df.to_csv(csv_buffer, index=False, encoding='utf-8-sig') csv_buffer.seek(0) return csv_buffer # 创建下载按钮 csv_data = export_to_csv() st.download_button( label="📥 导出为CSV", data=csv_data.getvalue(), file_name=f"lychee_rerank_results_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", mime="text/csv", key="export_csv", help="点击下载评分结果CSV文件" )5.4 导出功能详解
这段代码实现了以下功能:
1. 数据准备
- 将评分结果、查询语句、指令等信息整理成结构化的数据
- 添加时间戳,记录导出时间
- 使用pandas DataFrame进行数据组织
2. CSV文件生成
- 使用
utf-8-sig编码,确保中文正常显示 - 包含所有相关信息:排名、分数、文档内容、查询、指令、时间
- 文件名包含时间戳,避免重复
3. 下载按钮
- 使用Streamlit的
st.download_button组件 - 提供清晰的标签和提示信息
- 自动触发文件下载
4. 用户体验优化
- 只在有结果时显示导出按钮
- 使用醒目的图标和颜色
- 提供使用说明
6. 完整代码整合
为了让整个应用更加完整,这里提供一个整合后的关键部分代码示例。注意,这只是一个示例,你需要根据实际的app.py文件结构进行调整。
import streamlit as st import torch import pandas as pd import datetime from io import StringIO from transformers import AutoTokenizer, AutoModelForSequenceClassification # 添加自定义CSS美化界面 st.markdown(""" <style> /* 完整的CSS样式代码,同上文 */ </style> """, unsafe_allow_html=True) # 添加主容器 st.markdown('<div class="main">', unsafe_allow_html=True) # 应用标题 st.title("⚖️ Lychee-Rerank 相关性评分工具") # 侧边栏配置 with st.sidebar: st.header("⚙️ 配置选项") # ... 原有的侧边栏配置代码 # 主界面 st.header("📝 输入评分内容") # 输入区域 instruction = st.text_area( "指令 (Instruction)", value="基于查询检索相关文档", help="自定义评分规则,指导模型如何判断相关性" ) query = st.text_input( "查询语句 (Query)", placeholder="请输入要查询的内容...", help="输入待匹配的查询语句" ) documents = st.text_area( "候选文档 (每行一条)", height=200, placeholder="请输入候选文档,每行一条...", help="支持批量输入多条文档,每行一条" ) # 计算按钮 if st.button("🚀 计算相关性分数", key="calculate"): # 这里添加计算逻辑 # ... # 显示结果 # ... # 保存结果到session_state st.session_state["last_results"] = results_data st.session_state["last_query"] = query st.session_state["last_instruction"] = instruction # 导出按钮(在结果显示之后) if "last_results" in st.session_state and st.session_state["last_results"]: st.markdown("---") st.subheader("💾 导出结果") # 导出功能实现 # ... # 关闭主容器 st.markdown('</div>', unsafe_allow_html=True)7. 实际效果展示
完成以上修改后,重新启动Lychee-Rerank应用,你会看到以下改进:
7.1 界面美化效果
整体视觉提升:
- 渐变色背景让页面更有层次感
- 圆角卡片设计使内容区域更加突出
- 统一的色彩方案提升专业感
输入区域优化:
- 输入框获得焦点时有明显的视觉反馈
- 更好的间距和排版提高可读性
- 悬停效果增强交互体验
结果展示改进:
- 每个结果项以卡片形式呈现,更加清晰
- 颜色编码直观显示相关性等级
- 进度条使用渐变色,视觉效果更好
- 排名徽章让结果更加醒目
7.2 导出功能体验
使用流程:
- 正常输入指令、查询和文档
- 点击计算按钮得到评分结果
- 结果区域下方会出现导出按钮
- 点击"导出为CSV"按钮下载文件
导出文件内容:下载的CSV文件包含以下列:
- 排名:文档的相关性排名
- 相关性分数:精确到6位小数的分数
- 文档内容:完整的文档文本
- 查询语句:本次评分使用的查询
- 评分指令:本次评分使用的指令
- 导出时间:文件生成的时间戳
文件示例:
排名,相关性分数,文档内容,查询语句,评分指令,导出时间 1,0.956231,机器学习是人工智能的一个分支...,什么是机器学习?,基于查询检索相关文档,2024-01-15 14:30:25 2,0.782145,深度学习是机器学习的一种...,什么是机器学习?,基于查询检索相关文档,2024-01-15 14:30:25 3,0.423567,Python是一种编程语言...,什么是机器学习?,基于查询检索相关文档,2024-01-15 14:30:258. 常见问题与解决方案
8.1 CSS样式不生效
问题:添加了CSS代码,但界面没有变化。
可能原因和解决方案:
CSS代码位置不对
- 确保CSS代码在
st.markdown函数中 - 确保
unsafe_allow_html=True参数已设置 - 将CSS代码放在页面渲染之前
- 确保CSS代码在
样式被覆盖
- Streamlit有默认样式,可能需要使用
!important提高优先级 - 检查CSS选择器是否正确
- Streamlit有默认样式,可能需要使用
缓存问题
- 清除浏览器缓存
- 重启Streamlit应用
- 使用
st.markdown的key参数强制刷新
8.2 导出按钮不显示
问题:计算了结果,但导出按钮没有出现。
检查步骤:
确认结果已保存
# 在计算按钮后添加调试信息 st.write("结果数量:", len(results)) st.write("session_state中有结果:", "last_results" in st.session_state)检查session_state
- 确保正确设置了
st.session_state["last_results"] - 确保结果不为空列表
- 确保正确设置了
按钮显示条件
- 确保导出按钮的显示条件正确
- 检查是否有语法错误
8.3 导出文件乱码
问题:导出的CSV文件中文显示乱码。
解决方案:
指定编码格式
# 使用utf-8-sig编码,兼容Excel df.to_csv(csv_buffer, index=False, encoding='utf-8-sig')Excel打开设置
- 用文本编辑器(如VS Code)打开确认编码
- Excel中导入时选择UTF-8编码
文件内容验证
# 导出前预览数据 st.write("导出数据预览:", df.head())
8.4 性能考虑
问题:添加CSS和导出功能后应用变慢。
优化建议:
CSS优化
- 将CSS代码移到单独的文件中
- 使用更简洁的选择器
- 避免复杂的动画效果
导出优化
- 对于大量数据,分页或限制导出数量
- 添加加载状态提示
- 使用更高效的数据处理方式
缓存利用
@st.cache_data def process_results(results): # 处理结果数据 return processed_data
9. 进阶定制建议
9.1 更多样式定制
如果你想让界面更加个性化,可以尝试:
主题颜色调整:
/* 修改主色调 */ :root { --primary-color: #4f46e5; --secondary-color: #7c3aed; --success-color: #10b981; } .stButton button { background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); }暗色主题支持:
@media (prefers-color-scheme: dark) { .main { background-color: rgba(30, 41, 59, 0.95); color: #f1f5f9; } .result-card { background: #1e293b; color: #f1f5f9; } }动画效果增强:
/* 添加加载动画 */ @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } .loading { animation: pulse 1.5s infinite; }9.2 导出功能扩展
支持更多格式:
# 添加JSON导出 json_data = df.to_json(orient="records", force_ascii=False) st.download_button( label="📥 导出为JSON", data=json_data, file_name="results.json", mime="application/json" ) # 添加Excel导出(需要openpyxl) excel_buffer = BytesIO() df.to_excel(excel_buffer, index=False, engine='openpyxl') excel_buffer.seek(0) st.download_button( label="📥 导出为Excel", data=excel_buffer, file_name="results.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" )添加导出选项:
# 让用户选择导出格式 export_format = st.radio( "选择导出格式", ["CSV", "JSON", "Excel"], horizontal=True ) if export_format == "CSV": # CSV导出逻辑 elif export_format == "JSON": # JSON导出逻辑 else: # Excel导出逻辑9.3 结果可视化增强
添加图表展示:
import plotly.express as px # 创建分数分布直方图 if results: scores = [r["score"] for r in results] fig = px.histogram( x=scores, nbins=20, title="相关性分数分布", labels={"x": "分数", "y": "数量"} ) st.plotly_chart(fig)添加统计信息:
if results: scores = [r["score"] for r in results] col1, col2, col3, col4 = st.columns(4) with col1: st.metric("平均分数", f"{np.mean(scores):.4f}") with col2: st.metric("最高分数", f"{max(scores):.4f}") with col3: st.metric("最低分数", f"{min(scores):.4f}") with col4: st.metric("文档数量", len(results))10. 总结
通过这篇教程,我们为Lychee-Rerank添加了两个非常实用的功能:界面美化和结果导出。这些改进虽然看起来只是表面工作,但实际上能显著提升工具的使用体验和专业感。
主要收获:
界面美化方面
- 学会了如何使用自定义CSS美化Streamlit应用
- 掌握了渐变背景、卡片设计、悬停效果等实用技巧
- 了解了如何创建响应式布局,适配不同设备
导出功能方面
- 实现了完整的CSV导出功能
- 学会了如何组织数据结构并生成文件
- 掌握了Streamlit下载按钮的使用方法
实际价值
- 提升了工具的视觉吸引力
- 增加了数据持久化能力
- 改善了用户体验和工作效率
下一步建议:
如果你还想进一步优化这个工具,可以考虑:
- 添加更多导出选项:支持JSON、Excel等格式
- 实现导入功能:从文件导入文档列表
- 添加批量处理:支持多个查询同时评分
- 集成更多模型:支持切换不同的rerank模型
- 添加API接口:提供程序化调用方式
最重要的是,现在你有了一个既美观又实用的本地相关性评分工具。无论是自己使用还是分享给团队,都能提供更好的体验。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。