見出し画像

本格的なエロチャットAIを作りたい その10(streamlitのtext_inputの動作を考察)

streamlitで画面を作った時、更新するのにst.rerun()を使いたくなる。
「使わない方がいい」とか言われるが、そもそもサクッと画面を作りたいからstreamlitを使っているわけで、そう考えるとサクッと画面更新するのにst.rerun()を使うのは当然だ。
と開き直って画面を作ってきたが、ちょっと壁にぶつかったものがあるので、順次まとめてみる。

やりたいことは単純で、

1:text_inputに入力した文字をボタンを押したら画面に反映して、text_inputをクリアする
2:6秒に1回、st.rerun()で画面のリフレッシュをするので、その時にtext_inputに入っている文字はクリアしたくない

では単純化したモデルで試してみよう

st.text_inputと再描画の挙動を調べる

import streamlit as st
import time

if 'status' not in st.session_state:
    st.session_state.status = ""

if 'user_input' not in st.session_state:
    st.session_state.user_input = ""

if 'merge_input' not in st.session_state:
    st.session_state.merge_input = ""

def update():
    st.session_state.merge_input += st.session_state.user_input
    st.session_state.user_input = ''

st.text_input("メッセージを入力してください:", key='user_input')

if st.button('更新',on_click=update):
    st.session_state.merge_input += st.session_state.user_input


st.session_state.status += ' rerun'

st.write('st.session_state.user_input = ',st.session_state.user_input)
st.write('st.session_state.merge_input = ',st.session_state.merge_input)
st.write('st.session_state.status = ',st.session_state.status)
time.sleep(10)
st.rerun()

動作は、
・ text_inputに文字を入力しただけだと6秒ごとのst.rerun()で文字がクリアされてしまう。
・ 文字入力後、Enterキーを押すとst.rerun()しても文字は消えない。

わかったこと:
・text_inputのst.session_state.user_inputへの代入条件:
 ・Enterキーを押す
 ・フォーカスを外す(text_input以外の場所を左クリックする)
・このとき再描写される(rerun表示が追加される)
・再描写されるとき、st.session_state.user_inputの値がtext_inputに反映される。

つまりtext_inputに文字を入力しただけではどこにも代入されてないのだ。
またstreamlitには、INKEYのような入力文字をリアルタイムで拾う機能はないらしい。

Streamlitのtext_inputの謎

ここでStreamlitのおかしなところなのだが、

def update():
    st.session_state.merge_input += st.session_state.user_input
    st.session_state.user_input = ''

の st.session_state.user_input = '' をコメントアウトすると、
text_input内に入力した文字はEnterキーを押さなくても(つまりst.session_state.user_inputに保存していなくても)クリアされない!

しかしこのupdate()はボタンを押したときにしか実行されないので、ここの文を変更して、ボタンを押さないときの挙動が変化するのは、おかしい!

だがどうもそれが事実らしい。
Perplexity AIで様々な提案がされたが、結局どれもダメだった。
受け入れざるを得ない………(涙)

対応案

rerunでクリアされてしまうのは避けられない事実なので、発想を変えて「不必要なときは入力させない」ことにする。

import streamlit as st
import time

if 'status' not in st.session_state:
    st.session_state.status = ""

if 'user_input' not in st.session_state:
    st.session_state.user_input = ""

if 'merge_input' not in st.session_state:
    st.session_state.merge_input = ""

if 'text_input_disable' not in st.session_state:
    st.session_state.text_input_disable = False

def update():
    st.session_state.merge_input += st.session_state.user_input
    st.session_state.user_input = ''
    st.session_state.text_input_disable = True

st.text_input("メッセージを入力してください:", key='user_input',disabled = st.session_state.text_input_disable)

if st.button('更新',on_click=update):
    st.session_state.merge_input += st.session_state.user_input


st.session_state.status += ' rerun'

st.write('st.session_state.user_input = ',st.session_state.user_input)
st.write('st.session_state.merge_input = ',st.session_state.merge_input)
st.write('st.session_state.status = ',st.session_state.status)

while True:
    time.sleep(5)
    if st.session_state.text_input_disable:
        st.session_state.text_input_disable = False
        st.rerun()


所感

ここで書いたように、本来無関係である箇所に追記するだけで動作が変わってしまう難しさを持つstreamlitのtext_inputのこのあたりの仕様は謎だし、説明しているものも見つけられなかった。
そもそもは「rerunするとtext_inputはクリアされる」と考えるべきらしいので、ここを深堀しても意味がないかもしれないが、素人にとって無関係の場所の変更で挙動が変わってしまうのは非常に困る。
このあたり、streamlitの出来はまだまだなのだろう。

この記事が気に入ったらサポートをしてみませんか?