Streamlit框架的定制化

news2024/11/24 2:45:35

Streamlit框架的定制化


最近做了一个关于streamlit框架的项目,颇有感触,所以在这里记录一下。

什么是streamlit?

Streamlit 是一个python的WEB UI库,它做了高度的封装以便于不懂后前端开发的人员也能轻松构建画面。你可以从官网进行详细的了解:https://docs.streamlit.io/library/api-reference 。

我的第一感受是,画面美观,很方便可以集成调用python的模块,不用像以前通过 ajax+web服务器的方式,省了很多麻烦。但是同时也会有一些问题,比如,定制化完成画面的布局等等,就会非常需要考验人的想象力了。

看完本文你能够了解到什么?

我将从实际遇到的问题入手,去探究如何去使用Streamlit 完成一些定制化的需求。

  • Streamlit 的执行原理和流程
  • Streamlit 如何自定义CSS
  • Streamlit 如何嵌入执行js
  • Streamlit 静态资源
  • Streamlit 布局问题
  • Streamlit 录音组件
  • Streamlit 事件如何处理
Streamlit 的执行原理和流程

用我不太专业的说法来讲就是,不论画面做了任何动作,streamlit都会从头到尾执行一次。
是的,这就代表当画面交互变得复杂,你需要很强的逻辑能力才能够驾驭,不然你可能会遇到各种问题。

Streamlit 如何自定义CSS

这部分其实官方有提到,参考以下代码即可实现:

global_css = open("bfs/global.css", encoding="utf-8").read()
st.markdown(f"""<style>{global_css}</style>""", unsafe_allow_html=True)

建议紧跟在 st.set_page_config 语句之后,因为代码总是自上而下执行,可以确保后续布局渲染时可以立即应用上css, 不然可能会出现 先出现布局后,位置再发生变化的情况。

另外,顺带分享一个两个选择器——索引选择器(官方可能不这么说)和属性选择器。

Section[data-testid='stSidebar'] div[data-testid='stVerticalBlock'] > div:nth-of-type(1){
	position: absolute;
	top: -80px;
	width: 130px;
	height: 35px
}

Section[data-testid='stSidebar'] 用于选中session元素属性 data-testid=‘stSidebar’ 的元素,div:nth-of-type(n) 用于选中 第 n 个div元素,注意这里的 n 从 1 开始。

这两个选择器基本上能够满足 streamlit 的布局需求。

如何嵌入执行js

官方也提到过,请参考以下代码:

global_js = open("bfs/global.js", encoding="utf-8").read()
st.components.v1.html(f"""<script >{global_js}</script>""", height=0, width=0)

这部分需要特别注意,使用这种方式实际上会在画面中 追加一个iframe 去执行我们的js代码。
但是 iframe 是完全独立的环境,这会导致你js获取dom元素会找不到,所以需要使用以下的代码跳出 iframe

let _document = window.parent.document;

在文件开头加入这句话,然后后续就可以使用 _document 愉快的使用js操作画面啦。

Streamlit 静态资源

这部分可以在自动 streamlit app时加入 --server.enableStaticServing=true 参数。

streamlit run frontend/app.py --browser.gatherUsageStats=False --server.enableStaticServing=true

它会将相对位置的 static 目录挂在为静态资源目录。这里的目录会指定为 frontend/static
请确保该文件夹存在。

项目启动成功以后,你就可以通过 http://xxx.xxx.xxx.xxx:xxxx/app/static/ 去访问该静态资源了。

这为我们布局提供了一些便利性,为什么这么说?
当我们需要向画面中添加一些图片的icon时,我个人在streamlit 有种解决方案:

1、通过 streamlit 的图片组件将图片放到画面上,然后通过 css 去调整布局。

# 全局css
global_css = open("bfs/global.css", encoding="utf-8").read()
st.markdown(f"""<style>{global_css}</style>""", unsafe_allow_html=True)

# 添加图片
st.image(Image.open("static/img/record.png"))

这种方式经实践是可行的,但是存在一些问题,画面加载时会有先展示原布局,然后才会被应用上css,另外st.image 生成的布局嵌套比较深,对于css定位不说麻烦,但是相对比较繁琐。最后偶尔会出现css应用不上去的情况,原因不明。

2、开启静态资源服务,由js完成图片icon的添加(推荐)
相对于第一种方式,我们需要开启静态资源服务,也就是本章节开始的部分。
然后后你可以定位到你需要的元素,去追加 img 元素,同时 可以指定 class,用于定位。
例:

let is_assistant = st_msg.querySelector("div[data-testid='stChatMessageContent']");
if (is_assistant) {
  if (is_assistant.getAttribute("aria-label").indexOf("from assistant") !== -1) {
   let img_ele = _document.createElement("img");
   img_ele.src = src;
   img_ele.width = 35;
   img_ele.height = 35;
   img_ele.classList.add("copy");
   img_ele.addEventListener("click", function() {
     img_ele.classList.add("hidden");
     const textarea = _document.createElement('textarea');
     _document.body.appendChild(textarea);
     textarea.textContent = img_ele.parentElement.innerText;
     textarea.select();
     if (_document.execCommand('copy')) {
       _document.execCommand('copy');
       // alert("copy success!")
       _document.body.removeChild(textarea);
       display_copied(img_ele);
     }
   });
   st_msg.append(img_ele);

上述代码是定位到 div[data-testid='stChatMessageContent'] 元素并追加图片的示例,同时设定了class并且添加了点击事件。

这种方式经实践比较稳定,已投入使用,未出现方式一中提到的问题。敬请参考。

Streamlit 布局问题

如何用Streamlit 实现我们想要的布局是一个问题,官方有一些组件可以使用。
https://docs.streamlit.io/library/api-reference/layout

其中 st.sidebar、st.columns、st.container 这三个基本上就可以完成我们需要的布局。

需要注意的点是,默认使用 st.* 是会加到整体的画面上的,我们以使用 with 关键字,这种情况下,会被加到 with 所在的容器当中。
例:

    with st.sidebar:
        # 2023/11/20 張 Add Start
        st.image(Image.open(f"{static_dir}/img/logo.jpg"))

上述代码中的 logo.jpg 就会被加到 侧边栏当中。

或者 在使用 container 等容器时,你可以先实例化一个 container,然后添加到容器。
例:

msg_container = st.container()
msg_container.image(Image.open(f"{static_dir}/img/logo.jpg"))

这样 logo.jpg 就会被加到 msg_container 中。
建议将需要的组件都分类装到各自的container中,这样会省不少事儿。

Streamlit 录音组件

这个部分,也有一些坑。这里就推荐一下吧~

1、 audio-recorder-streamlit
https://pypi.org/project/audio-recorder-streamlit/

示例代码:

import streamlit as st

from audio_recorder_streamlit import audio_recorder

audio_bytes = audio_recorder()
if audio_bytes:
    st.audio(audio_bytes, format="audio/wav")

效果:
在这里插入图片描述

2、streamlit-audiorecorder
https://pypi.org/project/streamlit-audiorecorder/

示例代码:

import streamlit as st
from audiorecorder import audiorecorder

st.title("Audio Recorder")
audio = audiorecorder("Click to record", "Click to stop recording")

if len(audio) > 0:
    # To play audio in frontend:
    st.audio(audio.export().read())  

效果:
在这里插入图片描述

3、streamlit-audiorec
https://pypi.org/project/streamlit-audiorec/

示例代码:

import streamlit as st
from st_audiorec import st_audiorec
wav_audio_data = st_audiorec()

if wav_audio_data is not None:
    st.audio(wav_audio_data, format='audio/wav')

效果:
在这里插入图片描述
这种方式虽然很炫酷~ 但是经测试,整合到项目之后非常慢,不知道原因。
目前笔者使用的是 第二种方式。

Streamlit 事件如何处理

这么说吧~ 我觉得Streamlit 没有所谓的事件处理,它所有的动作都会触发整个apps重新加载。
由于这一机制,你只能保存状态,通过flag去做处理。

例:

import streamlit as st
from audio_recorder_streamlit import audio_recorder


audio_bytes = audio_recorder()
if audio_bytes:
    text = "识别后的文字"
    st.write(text)

st.button("TEST")

上述是一段代码,模拟用户语音输入后识别文字,然后显示在文本框中。
在这里插入图片描述
然而,当我清空文本框之后,它又会出现。

细心地小伙伴会发现是 if audio_bytes: 这个判断条件的问题,清空文本框后 录音内容并未被清空,所以当它被重新加载时又会出现。

清空不就好了?很遗憾,目前我无法做到,当然有经验的小伙伴可以共有一下。

这里采用迂回使用flag配合js的方式来实现。

在这里插入图片描述
如上图所示,追加了一个录音按钮和录音图标,由 录音图标 去控制开始结束录音,完成录音时同时点击录音按钮 完成flag的设定。
当然录音图标的 追加与点击逻辑 由js实现,这部分我就不细说了。

最后判断 需要的识别条件就可以设置成该flag。
示例代码:

import streamlit as st
from audio_recorder_streamlit import audio_recorder

if "record_flag" not in st.session_state:
    st.session_state["record_flag"] = False

if "text" not in st.session_state:
    st.session_state["text"] = ""

audio_bytes = audio_recorder()
if st.session_state["record_flag"]:
    st.session_state["text"] = "识别后的文字"
    st.session_state["record_flag"] = False


st.text_area(label="", key="text")

def record():
    st.session_state["record_flag"] = True

st.button("Record", on_click=record)

用这种思路可以实现相对于比较复杂的逻辑。

另外,由于 streamlit 的刷新机制,某些情况下是部分刷新,这部分,我没太明白,导致某些情况下达不到我们预期的效果;比如笔者遇到的问题,需要给 chatgpt 回答的内容添加点击事件,但是用户发消息时并未触发 js的运行,导致事件没绑定上。

这种情况,可以使用js的 观察者对象,监听Dom元素的变化。

示例代码:

const msg_observer = new MutationObserver((mutationsList, observer) => {
  add_copy_icon();
});
// 配置观察选项
const msg_config = { attributes: false, childList: true, subtree: true };
let msg_container = _document.querySelector('section.main .block-container');
msg_observer.observe(msg_container, msg_config);

这样做了之后,一旦 msg_container 元素发生变化就会执行 add_copy_icon 方法,以刷新监听事件。

以上就是本次关于 Streamlit 的使用心得,基本上能够应对 Streamlit 各种复杂的需求 ,当然你可以选择使用 Streamlit 的自定义组件去解决该问题,不在本次的讨论范畴。

欢迎大家留言探讨!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1289200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

你真的掌握结构体了么?结构体习题(C语言)

前言 上一期博客我们学习了结构体的相关知识&#xff08;上期链接&#xff09;&#xff0c;但是学了不练也是不行的&#xff0c;我们今天讲给大家分享两道有点恶心的题目&#xff0c;让大家来加深对结构体的理解&#xff0c;那么话不多说我们现在开始吧&#xff01; 第一题 有…

zabbix 进阶

zabbix的字段发现机制&#xff1a; zabbix客户端主动和服务端联系&#xff0c;将自己的地址和端口发送服务端实现字段添加监控主机。 客户端是主动一方。 缺点&#xff1a;自定义网段中主机数量太多&#xff0c;登记耗时会很久&#xff0c;而且这个自动发现机制不是很稳定。…

Hadoop学习笔记(HDP)-Part.20 安装Flume

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

AIGC+医疗专题:生成式人工智能于医疗健康

今天分享的是AI系列深度研究报告&#xff1a;《AIGC医疗专题&#xff1a;生成式人工智能于医疗健康》。 &#xff08;报告出品方&#xff1a;AREFACT&#xff09; 报告共计&#xff1a;23页 医疗保健中生成性人工智能的崛起: 在承诺与控制之间导航 Generative Al已经历了大规…

Python-代码块缩进详解

python中&#xff0c;if后面没有&#xff08;&#xff09;&#xff0c;执行代码块也没有{} 而是以&#xff1a;为结尾 代码块以缩进的形式书写&#xff1a; a input("请输入一个整数&#xff1a;") if a 1:print(aaaa) print(bbbb)#此时这一行代码就与判断条件无关…

数据结构-02-链表

相比数组&#xff0c;链表是一种稍微复杂一点的数据结构。掌握起来也要比数组稍难一些。这两个非常基础、非常常用的数据结构。 1-链表结构 数组需要一块连续的内存空间来存储&#xff0c;对内存的要求比较高。如果我们申请一个20MB大小的数组&#xff0c;当内存中没有连续的、…

python打包exe,打包好后,启动exe报错找不到paddleocr

目录 1、安装pyinstaller 2、生成脚本文件的.spce文件 3、资源文件配置 4、生成exe文件 5、使用了paddleocr启动exe后报错 6、配置.spce文件 7、重新生成exe文件 8、关于图片找不到的问题 参考&#xff1a;PaddleOCR打包exe--Pyinstaller_paddleocr 打包exe_mjiansun的博…

智能监控/安防监控视频平台EasyCVR下级更新目录表出现离线情况的两种解决方案

GB28181安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备…

esp32使用命令查看芯片flash大小以及PSRAM的大小

在idf.py命令窗口中输入 esptool.py -p COM* flash_id 其中COM*是连接你的esp32芯片的端口号。

打工人副业变现秘籍,某多/某手变现底层引擎-StableDiffusionWebUI界面基本布局和操作

一、界面设置 文生图&#xff1a;根据文本提示生成图像 图生图&#xff1a;图像生成图像&#xff1b;功能很强大&#xff0c;自己在后续使用中探索。 后期处理&#xff1a;图片处理&#xff1b;功能很强大&#xff0c;自己在后续使用中探索。 PNG信息&#xff1a;这是一个快…

“轻松管理视频文件:高效归类与统一重命名“

随着电子设备的普及&#xff0c;我们的视频文件可能来自各种不同的源头&#xff0c;如何高效地管理和查找这些文件成为了一个问题。今天&#xff0c;我们将为您提供一个完美的解决方案——自动归类并统一重命名视频文件。 首先&#xff0c;第一步&#xff0c;我们要进入文件批…

基于openEuler20.03安装openGauss5.0.0及安装DBMind

基于openEuler20.03安装openGauss5.0.0及安装DBMind 一、环境说明二、安装部署三、问题及解决 一、环境说明 虚拟机&#xff1a;VirtualBox操作系统&#xff1a;openEuler20.3LTS &#xff08;x86&#xff09;数据库&#xff1a;openGauss5.0.0 (x86)DBMind&#xff1a;dbmind…

智能优化算法应用:基于学生心理学算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于学生心理学算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于学生心理学算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.学生心理学算法4.实验参数设定5.算法结果…

Esxi6.0 安装web管理界面

安装6.0之后默认是vSphere Client进行远程连接&#xff0c;需要安装客户端&#xff0c;不是太方便。搜索发现还真可以实现web管理&#xff0c;步骤如下&#xff1a; 1、开启esxi的ssh&#xff0c;步骤如下图&#xff1a; 2、下载升级包esxui-signed-7119706.vib&#xff0c;上…

案例四:使用系统存储过程查看相关信息

1、什么是存储过程。 【1】模块化程序设计 【2】执行速度快&#xff0c;效率高 【3】减少网络流量 【4】具有良好的安全性 存储过程分为两类&#xff1a; 1、系统存储过程、2、用户自定义的存储过程 2、若xp_cmdshell作为服务器安全配置的一部分而被关闭&#xff0c;则需要配置…

力扣11.盛最多水的容器

题目描述 思路 用双指针法。 每次向内移动较短的那个板&#xff0c;能带来更大的效益。 代码 class Solution {public int maxArea(int[] height) {int res 0;int i 0,j height.length - 1;while(i < j){res height[i] < height[j] ? Math.max((j - i) * height…

祸害了人民3年的新冠消失了,但有些奇怪现象,让人百思不得其解

真是没想到啊&#xff0c;祸害我们3年的新冠病毒突然就消失了&#xff0c;但是紧接着呢&#xff0c;却有一个非常奇怪的现象出现了&#xff0c;真的是令人百思不得其解&#xff01; 新冠病毒&#xff0c;于2020年的开始&#xff0c;可以说根本就没有任何缓冲期&#xff0c;一开…

BUU SQL COURSE 1

四 发现有登录框&#xff0c;爆破半天也爆破不出来&#xff0c;只能从别的地方下手了 F12一下 发现了一个传参 进去发现id可以传参&#xff0c;sql注入一下试试 前三个都有回显&#xff0c;当id4的时候页面没有回显了&#xff0c;正好验证 了页面 有三个新闻 当order by 3的时…

docker安装及简单使用(Linux版本)

文章目录 前言一、docker安装二、docker命令pull&#xff08;安装镜像&#xff09;images&#xff08;查看镜像&#xff09;run&#xff08;创建容器&#xff09;删除容器exec&#xff08;进入运行中的容器&#xff09;常用命令 总结如有启发&#xff0c;可点赞收藏哟~ 前言 ht…

虚拟人如何在线下活动实现实时交互?动捕设备或为最优解

随着时代的进步&#xff0c;虚拟人凭借其打破时空界限、新颖差异化视觉效果等特点&#xff0c;在发布会、峰会等线下活动中发挥着重要作用&#xff0c;想要实现虚拟人在线下活动中实时交互&#xff0c;使用动捕设备可以让虚拟人化身虚拟主持人、虚拟主播、虚拟舞者演员等。 虚拟…