Skip to content

Commit

Permalink
Fix issue for st.camera_input and chat_input, add regression test for…
Browse files Browse the repository at this point in the history
… file_uploader (streamlit#7646)

Add playwright regression test for issue With st.file_uploader and st.chat_input, first message disappears streamlit#7556
Replicate changes from Ensure file_uploader doesn't trigger needless reruns streamlit#7641 for st.camera_input
  • Loading branch information
kajarenc authored and zyxue committed Apr 16, 2024
1 parent bad1ee0 commit 69a5c5e
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 4 deletions.
49 changes: 49 additions & 0 deletions e2e_playwright/st_chat_input_file_uploader_regression.py
@@ -0,0 +1,49 @@
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
#
# 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 License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import time

import streamlit as st

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

# A count to record the index of dialog
if "count" not in st.session_state:
st.session_state.count = 0

for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])

uploaded_files = st.sidebar.file_uploader(label="Upload", accept_multiple_files=True)

for file in uploaded_files:
st.sidebar.write(file.name)

if prompt := st.chat_input("What is up?"):
# Display user message in chat message container
with st.chat_message("user"):
st.markdown(prompt)
# Add user message to chat history
st.session_state.messages.append({"role": "user", "content": prompt})
st.session_state.count += 1

# Sleep one second here to simulate the process of assistant.
time.sleep(1)
with st.chat_message("assistant"):
assistant = f"Good at {st.session_state.count}"
st.markdown(assistant)
# Add assistant message to chat history
st.session_state.messages.append({"role": "assistant", "content": assistant})
27 changes: 27 additions & 0 deletions e2e_playwright/st_chat_input_file_uploader_regression_test.py
@@ -0,0 +1,27 @@
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
#
# 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 License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from playwright.sync_api import Page, expect

from e2e_playwright.conftest import wait_for_app_run


def test_regression_with_file_uploader_and_chat_input(app: Page):
"""Test issue described in https://github.com/streamlit/streamlit/issues/7556."""
chat_input_element = app.get_by_test_id("stChatInput").first
chat_input_element.fill("Hello world!")
chat_input_element.press("Enter")
wait_for_app_run(app)
last_chat_message = app.get_by_test_id("stChatMessageContent").last
expect(last_chat_message).to_have_text("Good at 1", use_inner_text=True)
Expand Up @@ -79,6 +79,7 @@ describe("CameraInput widget", () => {
enableFetchMocks()
it("renders without crashing", () => {
const props = getProps()
jest.spyOn(props.widgetMgr, "setFileUploaderStateValue")
const wrapper = shallow(<CameraInput {...props} />)
const instance = wrapper.instance() as CameraInput

Expand All @@ -87,6 +88,8 @@ describe("CameraInput widget", () => {

expect(wrapper.find(WebcamComponent)).toHaveLength(1)
expect(wrapper.find(StyledBox)).toHaveLength(0)
// WidgetStateManager should have been called on mounting
expect(props.widgetMgr.setFileUploaderStateValue).toHaveBeenCalledTimes(1)
})

it("sets initial value properly if non-empty", () => {
Expand Down
21 changes: 17 additions & 4 deletions frontend/lib/src/components/widgets/CameraInput/CameraInput.tsx
Expand Up @@ -261,9 +261,6 @@ class CameraInput extends React.PureComponent<Props, State> {
// If we have had no completed uploads, our widgetValue will be
// undefined, and we can early-out of the state update.
const newWidgetValue = this.createWidgetValue()
if (newWidgetValue === undefined) {
return
}

const { element, widgetMgr } = this.props

Expand All @@ -276,7 +273,23 @@ class CameraInput extends React.PureComponent<Props, State> {
}
}

private createWidgetValue(): FileUploaderStateProto | undefined {
public componentDidMount(): void {
const newWidgetValue = this.createWidgetValue()
const { element, widgetMgr } = this.props

// Set the state value on mount, to avoid triggering an extra rerun after
// the first rerun.
// We use same primitives as in file uploader widget,
// since simanticly camera_input is just a special case of file uploader.
const prevWidgetValue = widgetMgr.getFileUploaderStateValue(element)
if (prevWidgetValue === undefined) {
widgetMgr.setFileUploaderStateValue(element, newWidgetValue, {
fromUi: false,
})
}
}

private createWidgetValue(): FileUploaderStateProto {
const uploadedFileInfo: UploadedFileInfoProto[] = this.state.files
.filter(f => f.status.type === "uploaded")
.map(f => {
Expand Down

0 comments on commit 69a5c5e

Please sign in to comment.