项目地址:小汐创作助手: 小汐创作助手 (gitee.com)
为了能够快速完成页面的开发,提高开发速度降低开发成本。因此,我们整个项目,没有直接采用原来的前后端分离方案,而是直接使用streamlit完成搭建。streamlit是一款基于Python编写的前端机器学习组件库。简单高效,内置丰富的组件,同时我们也可以使用css对部分样式进行改造处理。 https://docs.streamlit.io/ 那么很显然,我们本篇博文的目的就是如何快速来实现,我们先前预设的UI效果。
首页: 对话助手 小说视频生成 设置页面 这里我们会简单介绍一下,我们在项目当中使用到的基本组件。当然streamlit本身还有非常丰富的组件,可以去查看官方文档探索,虽然是英文的,但是可以使用浏览器翻译来快速阅览,查找文档。文档当中有丰富的示例,因此可以直接看到效果,然后拿过来进行修改。
那么废话不多说,我们来看到我们整个页面是如何开始完成编写的。这里我们按照模块去进行拆分,希望,通过本篇博文,读者应该可以,将我们的整体页面跑起来,这样的话,我们接下来就只需关系到我们的功能实现即可了。
页面侧边导航
在开始之前,我们需要简单了解一下关于streamlit的一些机制的问题,首先的话,streamlit是支持热加载的。整个执行过程大致是这样的:1. 页面展示 2. 监听事件 3.执行事件函数 4. 重新刷新页面 因此这里要注意,在重新刷新页面的时候,是相当于把你写的代码重新执行了一遍的,因此,你先前保留在内存当中的变量都是会被重置的,因此如果要进行状态的保存的话,需要使用到它提供的session机制来进行保存。 那么这个事件的触发,执行,包括:按钮的点击,文本框的触发。而我们的侧边栏导航的实现,是通过单选框来进行实现的,因此每一次选择的话,其实都相当于重新运行了一下界面,只是我们会把一些状态进行保存而已。因此这一点需要特别注意🍡
整个侧边导航的实现非常简单:
在切换单选按钮时,执行不同的页面展示函数进行实现。同时为了满足我们的要求,我们这里重新修改了一下css。这块你可能比较好奇,为什么这个css的选择器那么奇怪。其实这个是streamit渲染之后,对应的容器的div的class。这个可以通过控制台进行获取。为了让内容显示可以有更大的空间,这里我们做了这样的修改:
首页🐱🏍
首页的话,主要是对项目的介绍,因此页面的形式和内容比较简单。这里的话,我们是直接使用st.markdown来实现文字的展示的。
当然,在这里,我们使用到一些配置相关的东西,但是这里没有涉及到具体的功能实现,因此我们不需要太关心。
到此,我们把我们最开始需要运行的main.py文件进行了实现。这将作为我们的整个程序的主入口。完整代码如下:
对话助手
我想在这块,你应该注意到了,我们还导入了如下包:
实际上,这个就是我们接下来需要逐一实现的页面。 那么这块我们还是先来看到我们的对话助手。注意,在我们本篇文章里面,还是先讨论我们如何实现这个页面,而不涉及到我们具体的实现,具体的实现,我们将在具体的章节讲到。 那么关于对话助手的实现的话,还是比较简单的,这里我们使用了streamlit提供的专门的对话组件,所以实现很快,并且实现效果还是很不错的。这个风格我也挺喜欢的,当然这里还是没有做历史话题记录,因为如果要做的话,那么我们这里最好还是要设计一套用户系统,这样的话,开发事件又上去了。 那么对话助手的实现如下:
那么在这块,我们的重点(后面实现功能的时候)就是: 这块代码的返回结果需要配合到我们的真正的AI返回。当然这块为了简便,我们也是实现了打字机特效,只是这个特效的前提不是以流式调用openai得到的,而是一次性拿到结果,然后再本地实现打字机特效进行加载的。这样的话,实现更加简答,而且一次性传输,虽然总体上慢一点,但是开销也小一点。
设置页面🍭
这块的话,我们先聊哪个小说视频页面是怎么实现的,因为这个的话还是比较复杂的。但是我们可以先聊一聊,这个设置页面,这个页面相对简单。 那么在这里的话,我们也是用到了streamlit的布局系统: streamlit默认是垂直布局的,也就是所有的元素默认往下放置的。那么当我们要实现刚刚的这样的效果的时候,就不得不使用到水平布局,使用水平布局非常方便:
这样一来就直接完成了布局,这默认是1:1,所以你还可以这样传递参数:
这样的话,左边比右边就是1:2布局。
okey,那么接下来的话,我们直接看到总体代码吧:
这里的话,我们用到了关于config的信息,那么关于config的话,其实无法也就这几条:
只不过是这样读取了一下而已:
因为我们设置还是需要将配置里面的一些信息进行展示的😃
视频生成页面🦁
之后的话,就是我们的视频生成页面了,这个部分确实还是比较麻烦的。而且有几个小细节需要注意。当然,总体的页面实现还是不复杂的,我们开始依次进行拆解。
页面布局😃
毫无以为,我们还是先来看到我们的页面布局吧: 框起来的都是意味着,我们使用到了对应的水平布局的:
所以这里废话不多少,我们先来看到我们是怎么吧我们的组件给布置上去的,先不讨论,怎么实现功能。这里我们看到我们专门的page方法。
当然在这里我们还绑定了一些函数,这些函数我们先默认实现都是空,先不管。
数据加载
现在我们看到了我们完成了基本的页面布局,并且把我们的组件都搞上去了。但是细心的你应该是发现了,那就是这部分是动态加载的数据。 那么这块的话,就不得不说回到我们先前说的关于streamlit的特性,那就是我们的streamlit再执行一个方法,按钮(被触发)之后,我们的整个页面会重新刷新,所以如果,你执行完毕之后,能够把数据存起来,那么他会自动刷新页面,然后我们加载的组件又是根据数据来的,这样的话,就完成了我们的数据的动态的添加操作了。那么在这块的话,我们主要看到我们数据张什么样子:
那么重点是看到这一块:
那么这里的话,其实就是我们需要唯一注意的点,在页面的展示阶段。当然我们接下来还有几个细节需要注意。
注意细节☠
我们刚刚谈到,关于streamlit的执行机制的问题,因此的话,虽然这个执行机制非常的好,可以帮助我们及时将数据刷新过来,这一点有点类似于vue的响应式。(说到这个,最近也有研究,有机会可以聊聊如何通过proxy+观察者模式实现这个数据的双向绑定)
- text_area 获取输入值,这里我们获取输入值,需要使用这里面绑定的key 至于是为啥,因为这个组件默认就是触发的,所以只能通过这种方式来拿到值,具体的可以思考思考😊并不难想到
- 函数的绑定,同样由于刷新机制的问题,在弹窗当中的按钮绑定事件,只能使用on_click来绑定,才能保证触发。为什么同样是刚刚的原因。总结就是:同一时刻(只能有一个方法运行(按钮))on_click绑定类似于call_back的方式,所以一定会触发。总结就是,当你不确定的时候,直接使用on_click 绑定事件。而不是if来判断触发
那么到这块这边的细节就这些了,在实际实现的时候,还有细节,我们到时候再说。