Streamlit で ChatGPT みたいに, テキストを word ごとにインタラクティブ(streaming)表示したいメモ
ChatGPT っぽいの Streamlit でほしいネ...
Streamlitで自分用ChatGPTを作る
streamlit-chat で Chat UI あるけど, インタラクティブ(streamling)表示できん...
Streamlit UI で無理やり頑張ってみた!
背景
Streamlit の UI widgets は基本インタラクティブアップデートみたいなのは考えられていません. たとえばテキスト領域でテキストの内容変わった場合, UI 全部再描画で対応となります.
st.experimental_rerun()
で, 描画のリフレッシュをスクリプト側からリクエストすることができます.
ただ, UI widgets の数に変更なければチラツキとかレイアウト崩れはないですが, UI 数が多いと描画が遅くなりますので注意です.
方法
スクリプトのグローバルでカウンタを持ち, 文字(word)を一つづつメッセージを追加していき, ページのリドローでむりくり頑張ります.
(80 年代の UI みたいな感じ)
streamlit 自体にはグローバルなタイマー(animation update trigger)は無いようです.
今回はボタン押したら 0.2 秒ごとに word を append してリドローする, で ChatGPT っぽさを出してみました.
widget は st.text_area
を使います.
import streamlit as st
import time
msg = "Originally a fishing village named Edo, the city became politically prominent in 1603, when it became the seat of the Tokugawa shogunate".split()
if "idx" not in st.session_state:
st.session_state["idx"] = 0
if "msglen" not in st.session_state:
st.session_state["msglen"] = len(msg)
def get_word():
idx = st.session_state["idx"]
#st.write(idx)
st.session_state["idx"] += 1
if idx < st.session_state["msglen"]:
return msg[idx]
else:
return ""
if "msg" not in st.session_state:
st.session_state["msg"] = "Tokyo: "
if "streaming" in st.session_state:
st.session_state["msg"] += get_word() + " "
text = st.text_area("Awesome Chat AI", value=st.session_state["msg"], disabled=False, key="bora")
if st.button("stream"):
st.session_state["idx"] = 0
st.session_state["streaming"] = True
if "streaming" in st.session_state:
if st.session_state["idx"] < st.session_state["msglen"]:
time.sleep(0.2)
st.experimental_rerun()
else:
del st.session_state["streaming"]
さらなる高みを目指して
chat メッセージ数増えると描画が遅くなるのが想定されるので, streamlit-chat のように React component で, ReactJS(JavaScript)側で streaming 表示頑張るのがよいでしょう.
st.text_area
だとフォント変えたりとかレイアウト変えたりとかできないですし.
TODO
- streamlit-chat のように React component で, ReactJS(JavaScript)側で streaming 表示頑張る
Discussion