Streamlit框架的定制化

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 的自定义组件去解决该问题,不在本次的讨论范畴。

欢迎大家留言探讨!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux文件结构与文件权限

基于centos了解Linux文件结构 了解一下文件类型 Linux采用的一切皆文件的思想&#xff0c;将硬件设备、软件等所有数据信息都以文件的形式呈现在用户面前&#xff0c;这就使得我们对计算机的管理更加方便。所以本篇文章会对Linux操作系统的文件结构和文件权限进行讲解。 首先…

halcon如何设置窗口背景颜色?

halcon窗口背景默认是黑色&#xff0c;有时候图片背景是黑色&#xff0c;不方便观察边缘&#xff0c;如果需要设置窗口背景颜色&#xff0c;可以使用如下算子。 设置窗口背景颜色&#xff1a;白色 set_window_param (WindowHandle, background_color, white) 设置白色后的效…

13款趣味性不错(炫酷)的前端动画特效及源码(预览获取)分享(附源码)

文字激光打印特效 基于canvas实现的动画特效&#xff0c;你既可以设置初始的打印文字也可以在下方输入文字可实现激光字体打印&#xff0c;精简易用。 预览获取 核心代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&q…

关于 mapboxgl 的常用方法及效果

给地图标记点 实现效果 /*** 在地图上添加标记点* point: [lng, lat]* color: #83f7a0*/addMarkerOnMap(point, color #83f7a0) {const marker new mapboxgl.Marker({draggable: false,color: color,}).setLngLat(point).addTo(this.map);this.markersList.push(marker);},…

从【注意力机制】开始的,零基础【大模型】系列

注意力机制 原理&#xff1a;从关注全部到关注重点软注意力-计算方式传统注意力问题 键值注意力&#xff1a;单标签的检索系统计算方式 多头注意力&#xff1a;多标签的检索系统自注意力&#xff1a;对输入数据内部关系进行预处理计算方式 Transformer 原理&#xff1a;从关注全…

医院预约挂号平台的设计与实现

摘 要 网络的空前发展给人们的工作和生活带来了极大的便利&#xff0c;信息技术已成为节约运营成本、提高工作效率的首选。相比之下&#xff0c;国内相当多的中小医院在医院预约工作中的手工工作比较保守&#xff0c;数据查询和存储成本都很高&#xff0c;但效率很低。为了使医…

docker-compose部署sonarqube 8.9 版本

官方部署文档注意需求版本 所以选择8.9版本 一、准备部署配置 1、持久化目录 rootlocalhost:/root# mkdir -p /data/sonar/postgres /data/sonar/sonarqube/data /data/sonar/sonarqube/logs /data/sonar/sonarqube/extensions rootlocalhost:/root# chmod 777 /data/sona…

天眼销为电销行业降低获客成本

当下&#xff0c;做电销的老板都有一个深刻体会&#xff1a;市场竞争越来越激烈&#xff0c;获客成本不断攀升&#xff0c;但效率不升返降&#xff0c;企业经营困难。特别是在这一两年&#xff0c;市场环境紧张&#xff0c;业务不好开展&#xff0c;更是雪上加霜。 销售也感觉…

Matlab 曲线动态绘制

axes(handles.axes1); % 选定所画坐标轴 figure也可 h1 animatedline; h1.Color b; h1.LineWidth 2; h1.LineStyle -; % 线属性设置 for i 1 : length(x)addpoints(h1,x(i),y(i)); % x/y为待绘制曲线数据drawnow;pause(0.01); % 画点间停顿 end 示例&#xff1a; figure…

BearPi Std 板从入门到放弃 - 引气入体篇(8)(ADC)

简介 基于前面的文章, 缩略STM32CubeMx创建项目的过程&#xff0c;直接添加ADC相关初 始化; 开发板 &#xff1a; Bearpi Std(小熊派标准板) 主芯片: STM32L431RCT6 LED : PC13 \ 推挽输出即可 \ 高电平点亮 串口: Usart1 ADC1: PC2步骤 创建STM32CubeMX LED/串口ADC1初始…

「音视频处理」音频编码AAC详解,低码率提高音质?

AAC&#xff08;高级音频编码&#xff09; 也称为 MPEG-4 音频。数码音频压缩和编码的标准方式。AAC 编码文件可与音乐光盘的质量相匹敌&#xff0c;且声音质量通常等同于或高于以相同或甚至更高的位速率编码的 MP3 文件。 我们按这样的顺序讨论 &#xff1a; 1、 封装格式的…

如何使用 Zotero 导出所选条目的 PDF 文件

如何使用 Zotero 导出所选条目的 PDF 文件 Zotero 是一款强大的参考文献管理工具&#xff0c;但它并不直接提供将整个文件夹导出为 PDF 的选项。不过&#xff0c;您可以使用以下步骤来导出您所选的 Zotero 条目中的 PDF 文件&#xff0c;无需额外的插件。 选择所需的 Zotero 条…

2023年危险化学品生产单位安全生产管理人员证考试题库及危险化学品生产单位安全生产管理人员试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年危险化学品生产单位安全生产管理人员证考试题库及危险化学品生产单位安全生产管理人员试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&a…

二叉树链式结构

1.前置说明 我们手动构建一棵二叉树&#xff1a; 注意&#xff1a;上述代码并不是创建二叉树的方式 从概念中可以看出&#xff0c;二叉树定义是递归式的&#xff0c;因此后序基本操作中基本都是按照该概念实现的 2.二叉树的遍历 2.1前序、中序以及后序遍历 学习二叉树结构&a…

基于c++版本链队列改-Python版本链队列基础理解

##基于链表的队列实现 可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”&#xff0c;规定队尾仅可添加节点&#xff0c;队首仅可删除节点。 ##图解 ##基于链表的队列实现代码 class ListNode:"""定义链表"""def __init__(self)…

Nexus搭建npm私库(角色管理、上传脚本)

安装Nexus 官网下载 https://www.sonatype.com/products/sonatype-nexus-oss-download 进入官网下载&#xff0c;最新下载方式需要输入个人信息才能下载了 选择对应的系统进行下载 Windows 推荐也下载 UNIX 版本&#xff08;Windows 版本配置比较难改&#xff09; 如果没有下…

​LeetCode解法汇总2477. 到达首都的最少油耗

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; 给你一棵 …

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

DynamicDataSource-CSDN博客 /** Copyright 2002-2020 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the L…

排序算法基本原理及实现2

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 &#x1f324;️冒泡排序 &#x1…

解决mybatis-plus中,当属性为空的时候,update方法、updateById方法无法set null,直接忽略了

问题描述 当indexId set 22的时候是可以set的 我们发现sql语句也是正常的 表中数据也被更改了 但是当我们indexId为空的时候 sql语句中没有了set indexId这一属性。。 既然属性都没了&#xff0c;表是肯定没做修改的 问题解决 在实体类对应的字段上加注解TableField(strategy…