streamlit 1.51.0__py3-none-any.whl → 1.52.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit/__init__.py +1 -0
- streamlit/commands/execution_control.py +89 -14
- streamlit/components/v1/component_arrow.py +7 -7
- streamlit/components/v2/__init__.py +59 -3
- streamlit/components/v2/bidi_component/main.py +161 -13
- streamlit/components/v2/bidi_component/serialization.py +13 -6
- streamlit/components/v2/component_manager.py +11 -3
- streamlit/components/v2/component_registry.py +18 -1
- streamlit/components/v2/types.py +2 -2
- streamlit/connections/snowflake_connection.py +1 -1
- streamlit/connections/snowpark_connection.py +1 -1
- streamlit/dataframe_util.py +18 -18
- streamlit/delta_generator.py +7 -0
- streamlit/delta_generator_singletons.py +8 -14
- streamlit/elements/alert.py +16 -0
- streamlit/elements/arrow.py +36 -6
- streamlit/elements/bokeh_chart.py +10 -78
- streamlit/elements/code.py +2 -2
- streamlit/elements/deck_gl_json_chart.py +1 -1
- streamlit/elements/exception.py +1 -1
- streamlit/elements/form.py +27 -0
- streamlit/elements/heading.py +60 -5
- streamlit/elements/html.py +13 -2
- streamlit/elements/image.py +1 -1
- streamlit/elements/layouts.py +28 -22
- streamlit/elements/lib/built_in_chart_utils.py +49 -16
- streamlit/elements/lib/color_util.py +1 -1
- streamlit/elements/lib/column_config_utils.py +6 -5
- streamlit/elements/lib/layout_utils.py +50 -0
- streamlit/elements/lib/pandas_styler_utils.py +17 -9
- streamlit/elements/lib/shortcut_utils.py +152 -0
- streamlit/elements/markdown.py +50 -3
- streamlit/elements/metric.py +31 -1
- streamlit/elements/plotly_chart.py +75 -6
- streamlit/elements/spinner.py +1 -1
- streamlit/elements/text.py +20 -3
- streamlit/elements/toast.py +2 -0
- streamlit/elements/vega_charts.py +17 -1
- streamlit/elements/widgets/audio_input.py +8 -7
- streamlit/elements/widgets/button.py +279 -40
- streamlit/elements/widgets/button_group.py +27 -2
- streamlit/elements/widgets/camera_input.py +1 -1
- streamlit/elements/widgets/chat.py +300 -42
- streamlit/elements/widgets/color_picker.py +7 -0
- streamlit/elements/widgets/data_editor.py +68 -28
- streamlit/elements/widgets/file_uploader.py +4 -1
- streamlit/elements/widgets/number_input.py +2 -0
- streamlit/elements/widgets/text_widgets.py +2 -0
- streamlit/elements/widgets/time_widgets.py +581 -9
- streamlit/errors.py +22 -0
- streamlit/git_util.py +1 -1
- streamlit/navigation/page.py +7 -0
- streamlit/net_util.py +2 -2
- streamlit/proto/Alert_pb2.pyi +3 -3
- streamlit/proto/AppPage_pb2.pyi +7 -1
- streamlit/proto/ArrowData_pb2.pyi +7 -1
- streamlit/proto/ArrowNamedDataSet_pb2.pyi +7 -1
- streamlit/proto/ArrowVegaLiteChart_pb2.pyi +7 -1
- streamlit/proto/Arrow_pb2.py +10 -10
- streamlit/proto/Arrow_pb2.pyi +19 -12
- streamlit/proto/AudioInput_pb2.pyi +7 -1
- streamlit/proto/Audio_pb2.pyi +7 -1
- streamlit/proto/AuthRedirect_pb2.pyi +7 -1
- streamlit/proto/AutoRerun_pb2.pyi +7 -1
- streamlit/proto/BackMsg_pb2.py +4 -2
- streamlit/proto/BackMsg_pb2.pyi +34 -4
- streamlit/proto/Balloons_pb2.pyi +7 -1
- streamlit/proto/BidiComponent_pb2.pyi +10 -4
- streamlit/proto/Block_pb2.pyi +35 -35
- streamlit/proto/BokehChart_pb2.pyi +7 -1
- streamlit/proto/ButtonGroup_pb2.pyi +9 -9
- streamlit/proto/Button_pb2.py +2 -2
- streamlit/proto/Button_pb2.pyi +11 -2
- streamlit/proto/CameraInput_pb2.pyi +7 -1
- streamlit/proto/ChatInput_pb2.py +6 -6
- streamlit/proto/ChatInput_pb2.pyi +18 -6
- streamlit/proto/Checkbox_pb2.pyi +3 -3
- streamlit/proto/ClientState_pb2.pyi +10 -4
- streamlit/proto/Code_pb2.pyi +7 -1
- streamlit/proto/ColorPicker_pb2.pyi +7 -1
- streamlit/proto/Common_pb2.py +3 -3
- streamlit/proto/Common_pb2.pyi +35 -23
- streamlit/proto/Components_pb2.pyi +19 -13
- streamlit/proto/DataFrame_pb2.pyi +55 -49
- streamlit/proto/DateInput_pb2.pyi +7 -1
- streamlit/proto/DateTimeInput_pb2.py +28 -0
- streamlit/proto/DateTimeInput_pb2.pyi +92 -0
- streamlit/proto/DeckGlJsonChart_pb2.pyi +3 -3
- streamlit/proto/Delta_pb2.pyi +7 -1
- streamlit/proto/DocString_pb2.pyi +10 -4
- streamlit/proto/DownloadButton_pb2.py +2 -2
- streamlit/proto/DownloadButton_pb2.pyi +16 -2
- streamlit/proto/Element_pb2.py +5 -3
- streamlit/proto/Element_pb2.pyi +23 -5
- streamlit/proto/Empty_pb2.pyi +7 -1
- streamlit/proto/Exception_pb2.pyi +7 -1
- streamlit/proto/Favicon_pb2.pyi +7 -1
- streamlit/proto/FileUploader_pb2.pyi +7 -1
- streamlit/proto/ForwardMsg_pb2.py +12 -10
- streamlit/proto/ForwardMsg_pb2.pyi +42 -15
- streamlit/proto/GapSize_pb2.pyi +4 -4
- streamlit/proto/GitInfo_pb2.pyi +3 -3
- streamlit/proto/GraphVizChart_pb2.pyi +7 -1
- streamlit/proto/Heading_pb2.pyi +7 -1
- streamlit/proto/HeightConfig_pb2.pyi +7 -1
- streamlit/proto/Html_pb2.py +2 -2
- streamlit/proto/Html_pb2.pyi +11 -2
- streamlit/proto/IFrame_pb2.pyi +7 -1
- streamlit/proto/Image_pb2.pyi +10 -4
- streamlit/proto/Json_pb2.pyi +7 -1
- streamlit/proto/LabelVisibilityMessage_pb2.pyi +3 -3
- streamlit/proto/LinkButton_pb2.py +2 -2
- streamlit/proto/LinkButton_pb2.pyi +15 -2
- streamlit/proto/Logo_pb2.pyi +7 -1
- streamlit/proto/Markdown_pb2.pyi +3 -3
- streamlit/proto/Metric_pb2.pyi +7 -7
- streamlit/proto/MetricsEvent_pb2.pyi +10 -4
- streamlit/proto/MultiSelect_pb2.pyi +7 -1
- streamlit/proto/NamedDataSet_pb2.pyi +7 -1
- streamlit/proto/Navigation_pb2.pyi +3 -3
- streamlit/proto/NewSession_pb2.pyi +40 -40
- streamlit/proto/NumberInput_pb2.pyi +3 -3
- streamlit/proto/PageConfig_pb2.pyi +7 -7
- streamlit/proto/PageInfo_pb2.pyi +7 -1
- streamlit/proto/PageLink_pb2.py +2 -2
- streamlit/proto/PageLink_pb2.pyi +11 -2
- streamlit/proto/PageNotFound_pb2.pyi +7 -1
- streamlit/proto/PageProfile_pb2.pyi +13 -7
- streamlit/proto/PagesChanged_pb2.pyi +7 -1
- streamlit/proto/ParentMessage_pb2.pyi +7 -1
- streamlit/proto/PlotlyChart_pb2.pyi +6 -6
- streamlit/proto/Progress_pb2.pyi +7 -1
- streamlit/proto/Radio_pb2.pyi +7 -1
- streamlit/proto/RootContainer_pb2.pyi +1 -1
- streamlit/proto/Selectbox_pb2.pyi +7 -1
- streamlit/proto/SessionEvent_pb2.pyi +7 -1
- streamlit/proto/SessionStatus_pb2.pyi +7 -1
- streamlit/proto/Skeleton_pb2.pyi +3 -3
- streamlit/proto/Slider_pb2.pyi +5 -5
- streamlit/proto/Snow_pb2.pyi +7 -1
- streamlit/proto/Space_pb2.pyi +7 -1
- streamlit/proto/Spinner_pb2.pyi +7 -1
- streamlit/proto/TextAlignmentConfig_pb2.py +29 -0
- streamlit/proto/TextAlignmentConfig_pb2.pyi +68 -0
- streamlit/proto/TextArea_pb2.pyi +7 -1
- streamlit/proto/TextInput_pb2.pyi +3 -3
- streamlit/proto/Text_pb2.pyi +7 -1
- streamlit/proto/TimeInput_pb2.pyi +7 -1
- streamlit/proto/Toast_pb2.pyi +7 -1
- streamlit/proto/VegaLiteChart_pb2.pyi +7 -1
- streamlit/proto/Video_pb2.pyi +6 -6
- streamlit/proto/WidgetStates_pb2.pyi +10 -4
- streamlit/proto/WidthConfig_pb2.pyi +7 -1
- streamlit/proto/openmetrics_data_model_pb2.pyi +52 -52
- streamlit/runtime/app_session.py +38 -1
- streamlit/runtime/caching/cache_data_api.py +1 -1
- streamlit/runtime/caching/cache_resource_api.py +2 -2
- streamlit/runtime/caching/cache_utils.py +1 -1
- streamlit/runtime/caching/hashing.py +1 -1
- streamlit/runtime/download_data_util.py +53 -0
- streamlit/runtime/forward_msg_queue.py +1 -0
- streamlit/runtime/media_file_manager.py +178 -2
- streamlit/runtime/metrics_util.py +87 -3
- streamlit/runtime/scriptrunner/script_runner.py +3 -1
- streamlit/runtime/state/query_params.py +80 -29
- streamlit/runtime/state/session_state.py +2 -2
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +530 -229
- streamlit/static/static/js/{ErrorOutline.esm.YoJdlW1p.js → ErrorOutline.esm.ZJDbmVTx.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.Ddx8VEYy.js → FileDownload.esm.Dx0vI3vH.js} +1 -1
- streamlit/static/static/js/{FileHelper.90EtOmj9.js → FileHelper.B7Ero7qQ.js} +3 -3
- streamlit/static/static/js/{FormClearHelper.BB1Km6eP.js → FormClearHelper.CG2XN1_g.js} +1 -1
- streamlit/static/static/js/IFrameUtil.DefezniK.js +1 -0
- streamlit/static/static/js/InputInstructions.Cj5-1zf6.js +1 -0
- streamlit/static/static/js/Particles.BfWfv0Aw.js +1 -0
- streamlit/static/static/js/{ProgressBar.DLY8H6nE.js → ProgressBar.CGQ8OgfO.js} +2 -2
- streamlit/static/static/js/StreamlitSyntaxHighlighter.DTKLpwhl.js +20 -0
- streamlit/static/static/js/{Toolbar.D8nHCkuz.js → Toolbar.B2qFUmd9.js} +1 -1
- streamlit/static/static/js/_arrayIncludes.B19Iyn2B.js +1 -0
- streamlit/static/static/js/_baseIndexOf.BTknn6Gb.js +1 -0
- streamlit/static/static/js/{base-input.CJGiNqed.js → base-input.o9tL8MDP.js} +4 -4
- streamlit/static/static/js/{checkbox.Cpdd482O.js → checkbox.0BeV1IBL.js} +1 -1
- streamlit/static/static/js/{createSuper.CuQIogbW.js → createSuper.RBO59fEm.js} +1 -1
- streamlit/static/static/js/data-grid-overlay-editor.CiTkUy0t.js +1 -0
- streamlit/static/static/js/{downloader.CN0K7xlu.js → downloader.DwNZg3Mw.js} +1 -1
- streamlit/static/static/js/embed.XT9xNd3F.js +195 -0
- streamlit/static/static/js/{es6.BJcsVXQ0.js → es6.x9KsYQg-.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.XzUvQqcZ.js → iframeResizer.contentWindow.ZVXpMPi0.js} +1 -1
- streamlit/static/static/js/index.5VPOamri.js +1 -0
- streamlit/static/static/js/index.8HslT92O.js +14 -0
- streamlit/static/static/js/index.AnXMIBz3.js +7 -0
- streamlit/static/static/js/index.B0yp3bM1.js +6 -0
- streamlit/static/static/js/index.B1fRb5wF.js +1 -0
- streamlit/static/static/js/index.B527JZdO.js +3 -0
- streamlit/static/static/js/index.BHgV-yW4.js +1 -0
- streamlit/static/static/js/index.BQr-XwGV.js +1 -0
- streamlit/static/static/js/index.BTtmaLDB.js +1 -0
- streamlit/static/static/js/index.BWB_91TA.js +1 -0
- streamlit/static/static/js/index.BfEKaEmw.js +1 -0
- streamlit/static/static/js/index.BfXjTO8b.js +1 -0
- streamlit/static/static/js/index.Bjy4NRu9.js +3 -0
- streamlit/static/static/js/index.Bu5JWpT_.js +1 -0
- streamlit/static/static/js/index.BuCx76ZV.js +1 -0
- streamlit/static/static/js/index.BxjzhVUb.js +2 -0
- streamlit/static/static/js/index.By55VdPY.js +1 -0
- streamlit/static/static/js/index.CF5MxTbK.js +1 -0
- streamlit/static/static/js/index.CLmq_z9K.js +1 -0
- streamlit/static/static/js/index.CNH4rdSz.js +1 -0
- streamlit/static/static/js/{index.D3GPA5k4.js → index.CTgm_-jO.js} +9 -40
- streamlit/static/static/js/index.C_rK-Swb.js +188 -0
- streamlit/static/static/js/index.CjozwSzS.js +1 -0
- streamlit/static/static/js/{index.DOFlg3dS.js → index.CkGVt6-G.js} +1 -1
- streamlit/static/static/js/index.CuvXOyER.js +2 -0
- streamlit/static/static/js/{index.B_dWA3vd.js → index.CyUHWoCC.js} +2 -2
- streamlit/static/static/js/index.CyroQtI4.js +2 -0
- streamlit/static/static/js/index.D6HmkoDm.js +263 -0
- streamlit/static/static/js/index.DAqCNvsO.js +1 -0
- streamlit/static/static/js/index.DB_w_CZQ.js +1 -0
- streamlit/static/static/js/index.DBalctjj.js +2 -0
- streamlit/static/static/js/index.DK0RFJUG.js +11 -0
- streamlit/static/static/js/index.DMxc2XFp.js +151 -0
- streamlit/static/static/js/index.DO5utP74.js +2 -0
- streamlit/static/static/js/index.DS7lf09n.js +1 -0
- streamlit/static/static/js/index.DWexTVLY.js +1 -0
- streamlit/static/static/js/index.DXxnU5ej.js +1 -0
- streamlit/static/static/js/index.DcU3uDvB.js +2 -0
- streamlit/static/static/js/index.DlltaH7J.js +1 -0
- streamlit/static/static/js/index.DpNTZz82.js +27 -0
- streamlit/static/static/js/index.Dr9HIhQw.js +1 -0
- streamlit/static/static/js/index.DsgAU5lc.js +1 -0
- streamlit/static/static/js/{index.DPUXkcQL.js → index.KfXqjDYy.js} +1 -1
- streamlit/static/static/js/index.PaidgjCs.js +1 -0
- streamlit/static/static/js/index.RJZuWCGA.js +1 -0
- streamlit/static/static/js/{index.CxIUUfab.js → index.hbeqcRTn.js} +53 -122
- streamlit/static/static/js/index.q5hIQwAY.js +1 -0
- streamlit/static/static/js/index.rORSX6IW.js +1 -0
- streamlit/static/static/js/index.uSX757_v.js +1 -0
- streamlit/static/static/js/index.x_QRaLMd.js +1 -0
- streamlit/static/static/js/{input.D4MN_FzN.js → input.D5oh9-aB.js} +2 -2
- streamlit/static/static/js/main.q9oGOg0H.js +13 -0
- streamlit/static/static/js/{memory.DrZjtdGT.js → memory.5kCSFUJS.js} +1 -1
- streamlit/static/static/js/moment.C3j7ZXd7.js +4 -0
- streamlit/static/static/js/number-overlay-editor.Cn_LsK8N.js +9 -0
- streamlit/static/static/js/pandasStylerUtils.BqhXt51_.js +1 -0
- streamlit/static/static/js/{possibleConstructorReturn.exeeJQEP.js → possibleConstructorReturn.DD9NK1Z8.js} +1 -1
- streamlit/static/static/js/{sandbox.ClO3IuUr.js → sandbox.DACSyz29.js} +1 -1
- streamlit/static/static/js/styled-components.C3R090At.js +1 -0
- streamlit/static/static/js/threshold.Q1mXg5rX.js +1 -0
- streamlit/static/static/js/throttle.B0GR3Iyz.js +1 -0
- streamlit/static/static/js/{timepicker.DAhu-vcF.js → timepicker.BdhzPxrv.js} +1 -1
- streamlit/static/static/js/timer.C2hYhUse.js +1 -0
- streamlit/static/static/js/{toConsumableArray.DNbljYEC.js → toConsumableArray.Db2pdqM2.js} +1 -1
- streamlit/static/static/js/uniqueId.CtqIr-Yh.js +1 -0
- streamlit/static/static/js/urls.BwSlolu9.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.D6sOH6oI.js → useBasicWidgetState.Bfp6TnSw.js} +1 -1
- streamlit/static/static/js/useIntlLocale.hRV75Xgj.js +12 -0
- streamlit/static/static/js/{useTextInputAutoExpand.4u3_GcuN.js → useTextInputAutoExpand.QepX7n8Y.js} +1 -1
- streamlit/static/static/js/useUpdateUiValue.DHx8TzX6.js +1 -0
- streamlit/static/static/js/useWaveformController.WxVzpzEX.js +1 -0
- streamlit/static/static/js/value.B4vHRSi7.js +1 -0
- streamlit/static/static/js/withCalculatedWidth.DcKeRSWJ.js +1 -0
- streamlit/static/static/js/withFullScreenWrapper.CrHddARq.js +1 -0
- streamlit/string_util.py +8 -1
- streamlit/testing/v1/app_test.py +15 -0
- streamlit/testing/v1/element_tree.py +62 -0
- streamlit/web/bootstrap.py +24 -0
- streamlit/web/server/oauth_authlib_routes.py +5 -2
- streamlit/web/server/upload_file_request_handler.py +16 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/METADATA +9 -5
- {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/RECORD +274 -239
- streamlit/static/static/js/InputInstructions.jhH15PqV.js +0 -1
- streamlit/static/static/js/Particles.DUsputn1.js +0 -1
- streamlit/static/static/js/data-grid-overlay-editor.2Ufgxc6y.js +0 -1
- streamlit/static/static/js/index.B1ZQh4P1.js +0 -1
- streamlit/static/static/js/index.BKstZk0M.js +0 -27
- streamlit/static/static/js/index.BMcFsUee.js +0 -1
- streamlit/static/static/js/index.BR-IdcTb.js +0 -2
- streamlit/static/static/js/index.BgnZEMVh.js +0 -1
- streamlit/static/static/js/index.BohqXifI.js +0 -1
- streamlit/static/static/js/index.Br5nxKNj.js +0 -2
- streamlit/static/static/js/index.BrIKVbNc.js +0 -3
- streamlit/static/static/js/index.BtWUPzle.js +0 -1
- streamlit/static/static/js/index.C0RLraek.js +0 -1
- streamlit/static/static/js/index.CAIjskgG.js +0 -1
- streamlit/static/static/js/index.CAj-7vWz.js +0 -949
- streamlit/static/static/js/index.CMtEit2O.js +0 -1
- streamlit/static/static/js/index.CkRlykEE.js +0 -12
- streamlit/static/static/js/index.CmN3FXfI.js +0 -1617
- streamlit/static/static/js/index.CwbFI1_-.js +0 -1
- streamlit/static/static/js/index.D2KPNy7e.js +0 -1
- streamlit/static/static/js/index.DGAh7DMq.js +0 -1
- streamlit/static/static/js/index.DKb_NvmG.js +0 -197
- streamlit/static/static/js/index.DMqgUYKq.js +0 -1
- streamlit/static/static/js/index.DX1xY89g.js +0 -1
- streamlit/static/static/js/index.DYATBCsq.js +0 -2
- streamlit/static/static/js/index.DaSmGJ76.js +0 -3
- streamlit/static/static/js/index.Dd7bMeLP.js +0 -1
- streamlit/static/static/js/index.DjmmgI5U.js +0 -1
- streamlit/static/static/js/index.Dq56CyM2.js +0 -1
- streamlit/static/static/js/index.DuiXaS5_.js +0 -7
- streamlit/static/static/js/index.DvFidMLe.js +0 -2
- streamlit/static/static/js/index.DwkhC5Pc.js +0 -1
- streamlit/static/static/js/index.Q-3sFn1v.js +0 -1
- streamlit/static/static/js/index.QJ5QO9sJ.js +0 -1
- streamlit/static/static/js/index.VwTaeety.js +0 -1
- streamlit/static/static/js/index.YOqQbeX8.js +0 -1
- streamlit/static/static/js/number-overlay-editor.DRwAw1In.js +0 -9
- streamlit/static/static/js/uniqueId.oG4Gvj1v.js +0 -1
- streamlit/static/static/js/useUpdateUiValue.F2R3eTeR.js +0 -1
- streamlit/static/static/js/withFullScreenWrapper.zothJIsI.js +0 -1
- {streamlit-1.51.0.data → streamlit-1.52.0.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/WHEEL +0 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/entry_points.txt +0 -0
- {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/top_level.txt +0 -0
|
@@ -28,6 +28,7 @@ from typing import (
|
|
|
28
28
|
overload,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
from streamlit import config
|
|
31
32
|
from streamlit.elements.lib.form_utils import current_form_id
|
|
32
33
|
from streamlit.elements.lib.layout_utils import (
|
|
33
34
|
LayoutConfig,
|
|
@@ -390,7 +391,7 @@ class ButtonGroupMixin:
|
|
|
390
391
|
>>> if selected is not None:
|
|
391
392
|
>>> st.markdown(f"You selected {sentiment_mapping[selected]} star(s).")
|
|
392
393
|
|
|
393
|
-
.. output
|
|
394
|
+
.. output::
|
|
394
395
|
https://doc-feedback-stars.streamlit.app/
|
|
395
396
|
height: 200px
|
|
396
397
|
|
|
@@ -403,7 +404,7 @@ class ButtonGroupMixin:
|
|
|
403
404
|
>>> if selected is not None:
|
|
404
405
|
>>> st.markdown(f"You selected: {sentiment_mapping[selected]}")
|
|
405
406
|
|
|
406
|
-
.. output
|
|
407
|
+
.. output::
|
|
407
408
|
https://doc-feedback-thumbs.streamlit.app/
|
|
408
409
|
height: 200px
|
|
409
410
|
|
|
@@ -423,6 +424,30 @@ class ButtonGroupMixin:
|
|
|
423
424
|
f" The passed default value is {default}"
|
|
424
425
|
)
|
|
425
426
|
|
|
427
|
+
# Convert small pixel widths to "content" to prevent icon wrapping.
|
|
428
|
+
# Calculate threshold based on theme.baseFontSize to be responsive to
|
|
429
|
+
# custom themes. The calculation is based on icon buttons sized in rem:
|
|
430
|
+
# - Button size: ~1.5rem (icon 1.25rem + padding 0.125rem x 2)
|
|
431
|
+
# - Gap: 0.125rem between buttons
|
|
432
|
+
# - thumbs: 2 buttons + 1 gap = 3.125rem
|
|
433
|
+
# - faces/stars: 5 buttons + 4 gaps = 8rem
|
|
434
|
+
base_font_size = config.get_option("theme.baseFontSize") or 16
|
|
435
|
+
button_size_rem = 1.5
|
|
436
|
+
gap_size_rem = 0.125
|
|
437
|
+
|
|
438
|
+
if options == "thumbs":
|
|
439
|
+
# 2 buttons + 1 gap
|
|
440
|
+
min_width_rem = 2 * button_size_rem + gap_size_rem
|
|
441
|
+
else:
|
|
442
|
+
# 5 buttons + 4 gaps (faces or stars)
|
|
443
|
+
min_width_rem = 5 * button_size_rem + 4 * gap_size_rem
|
|
444
|
+
|
|
445
|
+
# Convert rem to pixels based on base font size, add 10% buffer
|
|
446
|
+
min_width_threshold = int(min_width_rem * base_font_size * 1.1)
|
|
447
|
+
|
|
448
|
+
if isinstance(width, int) and width < min_width_threshold:
|
|
449
|
+
width = "content"
|
|
450
|
+
|
|
426
451
|
_default: list[int] | None = (
|
|
427
452
|
[options_indices[default]] if default is not None else None
|
|
428
453
|
)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
from collections.abc import Iterator, MutableMapping, Sequence
|
|
18
|
-
from dataclasses import dataclass
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
19
|
from enum import Enum
|
|
20
20
|
from typing import (
|
|
21
21
|
TYPE_CHECKING,
|
|
@@ -47,13 +47,18 @@ from streamlit.elements.lib.utils import (
|
|
|
47
47
|
save_for_app_testing,
|
|
48
48
|
to_key,
|
|
49
49
|
)
|
|
50
|
+
from streamlit.elements.widgets.audio_input import ALLOWED_SAMPLE_RATES
|
|
50
51
|
from streamlit.errors import StreamlitAPIException
|
|
51
52
|
from streamlit.proto.Block_pb2 import Block as BlockProto
|
|
52
53
|
from streamlit.proto.ChatInput_pb2 import ChatInput as ChatInputProto
|
|
53
54
|
from streamlit.proto.Common_pb2 import ChatInputValue as ChatInputValueProto
|
|
54
55
|
from streamlit.proto.Common_pb2 import FileUploaderState as FileUploaderStateProto
|
|
56
|
+
from streamlit.proto.Common_pb2 import UploadedFileInfo as UploadedFileInfoProto
|
|
55
57
|
from streamlit.proto.RootContainer_pb2 import RootContainer
|
|
56
58
|
from streamlit.proto.WidthConfig_pb2 import WidthConfig
|
|
59
|
+
from streamlit.runtime.memory_uploaded_file_manager import (
|
|
60
|
+
MemoryUploadedFileManager,
|
|
61
|
+
)
|
|
57
62
|
from streamlit.runtime.metrics_util import gather_metrics
|
|
58
63
|
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
|
59
64
|
from streamlit.runtime.state import (
|
|
@@ -70,34 +75,117 @@ if TYPE_CHECKING:
|
|
|
70
75
|
from streamlit.delta_generator import DeltaGenerator
|
|
71
76
|
|
|
72
77
|
|
|
78
|
+
# Audio file validation constants
|
|
79
|
+
_ACCEPTED_AUDIO_EXTENSION: str = ".wav"
|
|
80
|
+
_ACCEPTED_AUDIO_MIME_TYPES: frozenset[str] = frozenset(
|
|
81
|
+
{
|
|
82
|
+
"audio/wav",
|
|
83
|
+
"audio/wave",
|
|
84
|
+
"audio/x-wav",
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
73
89
|
@dataclass
|
|
74
90
|
class ChatInputValue(MutableMapping[str, Any]):
|
|
91
|
+
"""Represents the value returned by `st.chat_input` after user interaction.
|
|
92
|
+
|
|
93
|
+
This dataclass contains the user's input text, any files uploaded, and optionally
|
|
94
|
+
an audio recording. It provides a dict-like interface for accessing and modifying
|
|
95
|
+
its attributes.
|
|
96
|
+
|
|
97
|
+
Attributes
|
|
98
|
+
----------
|
|
99
|
+
text : str
|
|
100
|
+
The text input provided by the user.
|
|
101
|
+
files : list[UploadedFile]
|
|
102
|
+
A list of files uploaded by the user. Only present when accept_file=True.
|
|
103
|
+
audio : UploadedFile or None, optional
|
|
104
|
+
An audio recording uploaded by the user, if any. Only present when accept_audio=True.
|
|
105
|
+
|
|
106
|
+
Notes
|
|
107
|
+
-----
|
|
108
|
+
- Supports dict-like access via `__getitem__`, `__setitem__`, and `__delitem__`.
|
|
109
|
+
- Use `to_dict()` to convert the value to a standard dictionary.
|
|
110
|
+
- The 'files' key is only present when accept_file=True.
|
|
111
|
+
- The 'audio' key is only present when accept_audio=True.
|
|
112
|
+
"""
|
|
113
|
+
|
|
75
114
|
text: str
|
|
76
|
-
files: list[UploadedFile]
|
|
115
|
+
files: list[UploadedFile] = field(default_factory=list)
|
|
116
|
+
audio: UploadedFile | None = None
|
|
117
|
+
_include_files: bool = field(default=False, repr=False, compare=False)
|
|
118
|
+
_include_audio: bool = field(default=False, repr=False, compare=False)
|
|
119
|
+
_included_keys: tuple[str, ...] = field(init=False, repr=False, compare=False)
|
|
120
|
+
|
|
121
|
+
def __post_init__(self) -> None:
|
|
122
|
+
"""Compute and cache the included keys after initialization."""
|
|
123
|
+
keys: list[str] = ["text"]
|
|
124
|
+
if self._include_files:
|
|
125
|
+
keys.append("files")
|
|
126
|
+
if self._include_audio:
|
|
127
|
+
keys.append("audio")
|
|
128
|
+
object.__setattr__(self, "_included_keys", tuple(keys))
|
|
129
|
+
|
|
130
|
+
def _get_included_keys(self) -> tuple[str, ...]:
|
|
131
|
+
"""Return tuple of keys that should be exposed based on inclusion flags."""
|
|
132
|
+
return self._included_keys
|
|
77
133
|
|
|
78
134
|
def __len__(self) -> int:
|
|
79
|
-
return len(
|
|
135
|
+
return len(self._get_included_keys())
|
|
80
136
|
|
|
81
137
|
def __iter__(self) -> Iterator[str]:
|
|
82
|
-
return iter(
|
|
138
|
+
return iter(self._get_included_keys())
|
|
83
139
|
|
|
84
|
-
def
|
|
140
|
+
def __contains__(self, key: object) -> bool:
|
|
141
|
+
if not isinstance(key, str):
|
|
142
|
+
return False
|
|
143
|
+
return key in self._get_included_keys()
|
|
144
|
+
|
|
145
|
+
def __getitem__(self, item: str) -> str | list[UploadedFile] | UploadedFile | None:
|
|
146
|
+
if item not in self._get_included_keys():
|
|
147
|
+
raise KeyError(f"Invalid key: {item}")
|
|
85
148
|
try:
|
|
86
149
|
return getattr(self, item) # type: ignore[no-any-return]
|
|
87
150
|
except AttributeError:
|
|
88
151
|
raise KeyError(f"Invalid key: {item}") from None
|
|
89
152
|
|
|
153
|
+
def __getattribute__(self, name: str) -> Any:
|
|
154
|
+
# Intercept access to files/audio when they're excluded
|
|
155
|
+
# Use object.__getattribute__ to avoid infinite recursion
|
|
156
|
+
if name == "files" and not object.__getattribute__(self, "_include_files"):
|
|
157
|
+
raise AttributeError(
|
|
158
|
+
"'ChatInputValue' object has no attribute 'files' (accept_file=False)"
|
|
159
|
+
)
|
|
160
|
+
if name == "audio" and not object.__getattribute__(self, "_include_audio"):
|
|
161
|
+
raise AttributeError(
|
|
162
|
+
"'ChatInputValue' object has no attribute 'audio' (accept_audio=False)"
|
|
163
|
+
)
|
|
164
|
+
# For all other attributes, use normal lookup
|
|
165
|
+
return object.__getattribute__(self, name)
|
|
166
|
+
|
|
90
167
|
def __setitem__(self, key: str, value: Any) -> None:
|
|
168
|
+
if key not in self._get_included_keys():
|
|
169
|
+
raise KeyError(f"Invalid key: {key}")
|
|
91
170
|
setattr(self, key, value)
|
|
92
171
|
|
|
93
172
|
def __delitem__(self, key: str) -> None:
|
|
173
|
+
if key not in self._get_included_keys():
|
|
174
|
+
raise KeyError(f"Invalid key: {key}")
|
|
94
175
|
try:
|
|
95
176
|
delattr(self, key)
|
|
96
177
|
except AttributeError:
|
|
97
178
|
raise KeyError(f"Invalid key: {key}") from None
|
|
98
179
|
|
|
99
|
-
def to_dict(self) -> dict[str, str | list[UploadedFile]]:
|
|
100
|
-
|
|
180
|
+
def to_dict(self) -> dict[str, str | list[UploadedFile] | UploadedFile | None]:
|
|
181
|
+
result: dict[str, str | list[UploadedFile] | UploadedFile | None] = {
|
|
182
|
+
"text": self.text
|
|
183
|
+
}
|
|
184
|
+
if self._include_files:
|
|
185
|
+
result["files"] = self.files
|
|
186
|
+
if self._include_audio:
|
|
187
|
+
result["audio"] = self.audio
|
|
188
|
+
return result
|
|
101
189
|
|
|
102
190
|
|
|
103
191
|
class PresetNames(str, Enum):
|
|
@@ -188,7 +276,11 @@ def _pop_upload_files(
|
|
|
188
276
|
uploaded_file = UploadedFile(maybe_file_rec, f.file_urls)
|
|
189
277
|
collected_files.append(uploaded_file)
|
|
190
278
|
|
|
191
|
-
|
|
279
|
+
# Remove file from manager after creating UploadedFile object.
|
|
280
|
+
# Only MemoryUploadedFileManager implements remove_file.
|
|
281
|
+
# This explicit type check ensures we only use this cleanup logic
|
|
282
|
+
# with manager types we've explicitly approved.
|
|
283
|
+
if isinstance(ctx.uploaded_file_mgr, MemoryUploadedFileManager):
|
|
192
284
|
ctx.uploaded_file_mgr.remove_file(
|
|
193
285
|
session_id=ctx.session_id,
|
|
194
286
|
file_id=f.file_id,
|
|
@@ -197,9 +289,79 @@ def _pop_upload_files(
|
|
|
197
289
|
return collected_files
|
|
198
290
|
|
|
199
291
|
|
|
292
|
+
def _pop_audio_file(
|
|
293
|
+
audio_file_info: UploadedFileInfoProto | None,
|
|
294
|
+
) -> UploadedFile | None:
|
|
295
|
+
"""Extract and return a single audio file from the protobuf message.
|
|
296
|
+
|
|
297
|
+
Similar to _pop_upload_files but handles a single audio file instead of a list.
|
|
298
|
+
Validates that the uploaded file is a WAV file.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
audio_file_info : UploadedFileInfoProto or None
|
|
303
|
+
The protobuf message containing information about the uploaded audio file.
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
UploadedFile or None
|
|
308
|
+
The extracted audio file if available, None otherwise.
|
|
309
|
+
|
|
310
|
+
Raises
|
|
311
|
+
------
|
|
312
|
+
StreamlitAPIException
|
|
313
|
+
If the uploaded audio file does not have a `.wav` extension or its MIME type is not
|
|
314
|
+
one of the accepted WAV types (`audio/wav`, `audio/wave`, `audio/x-wav`).
|
|
315
|
+
"""
|
|
316
|
+
if audio_file_info is None:
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
ctx = get_script_run_ctx()
|
|
320
|
+
if ctx is None:
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
file_recs_list = ctx.uploaded_file_mgr.get_files(
|
|
324
|
+
session_id=ctx.session_id,
|
|
325
|
+
file_ids=[audio_file_info.file_id],
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if len(file_recs_list) == 0:
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
file_rec = file_recs_list[0]
|
|
332
|
+
uploaded_file = UploadedFile(file_rec, audio_file_info.file_urls)
|
|
333
|
+
|
|
334
|
+
# Validate that the file is a WAV file by checking extension and MIME type
|
|
335
|
+
if not uploaded_file.name.lower().endswith(_ACCEPTED_AUDIO_EXTENSION):
|
|
336
|
+
raise StreamlitAPIException(
|
|
337
|
+
f"Invalid file extension for audio input: `{uploaded_file.name}`. "
|
|
338
|
+
f"Only WAV files ({_ACCEPTED_AUDIO_EXTENSION}) are accepted."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Validate MIME type (browsers may send different variations of WAV MIME types)
|
|
342
|
+
if uploaded_file.type not in _ACCEPTED_AUDIO_MIME_TYPES:
|
|
343
|
+
raise StreamlitAPIException(
|
|
344
|
+
f"Invalid MIME type for audio input: `{uploaded_file.type}`. "
|
|
345
|
+
f"Expected one of {_ACCEPTED_AUDIO_MIME_TYPES}."
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Remove the file from the manager after creating the UploadedFile object.
|
|
349
|
+
# Only MemoryUploadedFileManager implements remove_file (not part of the
|
|
350
|
+
# UploadedFileManager Protocol). This explicit type check ensures we only
|
|
351
|
+
# use this cleanup logic with manager types we've explicitly approved.
|
|
352
|
+
if audio_file_info and isinstance(ctx.uploaded_file_mgr, MemoryUploadedFileManager):
|
|
353
|
+
ctx.uploaded_file_mgr.remove_file(
|
|
354
|
+
session_id=ctx.session_id,
|
|
355
|
+
file_id=audio_file_info.file_id,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return uploaded_file
|
|
359
|
+
|
|
360
|
+
|
|
200
361
|
@dataclass
|
|
201
362
|
class ChatInputSerde:
|
|
202
363
|
accept_files: bool = False
|
|
364
|
+
accept_audio: bool = False
|
|
203
365
|
allowed_types: Sequence[str] | None = None
|
|
204
366
|
|
|
205
367
|
def deserialize(
|
|
@@ -207,16 +369,24 @@ class ChatInputSerde:
|
|
|
207
369
|
) -> str | ChatInputValue | None:
|
|
208
370
|
if ui_value is None or not ui_value.HasField("data"):
|
|
209
371
|
return None
|
|
210
|
-
if not self.accept_files:
|
|
372
|
+
if not self.accept_files and not self.accept_audio:
|
|
211
373
|
return ui_value.data
|
|
212
374
|
uploaded_files = _pop_upload_files(ui_value.file_uploader_state)
|
|
213
375
|
for file in uploaded_files:
|
|
214
376
|
if self.allowed_types and not isinstance(file, DeletedFile):
|
|
215
377
|
enforce_filename_restriction(file.name, self.allowed_types)
|
|
216
378
|
|
|
379
|
+
# Extract audio file separately from the audio_file_info field
|
|
380
|
+
audio_file = _pop_audio_file(
|
|
381
|
+
ui_value.audio_file_info if ui_value.HasField("audio_file_info") else None
|
|
382
|
+
)
|
|
383
|
+
|
|
217
384
|
return ChatInputValue(
|
|
218
385
|
text=ui_value.data,
|
|
219
386
|
files=uploaded_files,
|
|
387
|
+
audio=audio_file,
|
|
388
|
+
_include_files=self.accept_files,
|
|
389
|
+
_include_audio=self.accept_audio,
|
|
220
390
|
)
|
|
221
391
|
|
|
222
392
|
def serialize(self, v: str | None) -> ChatInputValueProto:
|
|
@@ -282,6 +452,8 @@ class ChatMixin:
|
|
|
282
452
|
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
|
|
283
453
|
font library.
|
|
284
454
|
|
|
455
|
+
- ``"spinner"``: Displays a spinner as an icon.
|
|
456
|
+
|
|
285
457
|
.. |st.image| replace:: ``st.image``
|
|
286
458
|
.. _st.image: https://docs.streamlit.io/develop/api-reference/media/st.image
|
|
287
459
|
|
|
@@ -313,7 +485,7 @@ class ChatMixin:
|
|
|
313
485
|
... st.write("Hello 👋")
|
|
314
486
|
... st.line_chart(np.random.randn(30, 3))
|
|
315
487
|
|
|
316
|
-
.. output
|
|
488
|
+
.. output::
|
|
317
489
|
https://doc-chat-message-user.streamlit.app/
|
|
318
490
|
height: 450px
|
|
319
491
|
|
|
@@ -326,7 +498,7 @@ class ChatMixin:
|
|
|
326
498
|
>>> message.write("Hello human")
|
|
327
499
|
>>> message.bar_chart(np.random.randn(30, 3))
|
|
328
500
|
|
|
329
|
-
.. output
|
|
501
|
+
.. output::
|
|
330
502
|
https://doc-chat-message-user1.streamlit.app/
|
|
331
503
|
height: 450px
|
|
332
504
|
|
|
@@ -377,6 +549,7 @@ class ChatMixin:
|
|
|
377
549
|
max_chars: int | None = None,
|
|
378
550
|
accept_file: Literal[False] = False,
|
|
379
551
|
file_type: str | Sequence[str] | None = None,
|
|
552
|
+
accept_audio: Literal[False] = False,
|
|
380
553
|
disabled: bool = False,
|
|
381
554
|
on_submit: WidgetCallback | None = None,
|
|
382
555
|
args: WidgetArgs | None = None,
|
|
@@ -384,6 +557,24 @@ class ChatMixin:
|
|
|
384
557
|
width: WidthWithoutContent = "stretch",
|
|
385
558
|
) -> str | None: ...
|
|
386
559
|
|
|
560
|
+
@overload
|
|
561
|
+
def chat_input(
|
|
562
|
+
self,
|
|
563
|
+
placeholder: str = "Your message",
|
|
564
|
+
*,
|
|
565
|
+
key: Key | None = None,
|
|
566
|
+
max_chars: int | None = None,
|
|
567
|
+
accept_file: Literal[False] = False,
|
|
568
|
+
file_type: str | Sequence[str] | None = None,
|
|
569
|
+
accept_audio: Literal[True],
|
|
570
|
+
audio_sample_rate: int | None = 16000,
|
|
571
|
+
disabled: bool = False,
|
|
572
|
+
on_submit: WidgetCallback | None = None,
|
|
573
|
+
args: WidgetArgs | None = None,
|
|
574
|
+
kwargs: WidgetKwargs | None = None,
|
|
575
|
+
width: WidthWithoutContent = "stretch",
|
|
576
|
+
) -> ChatInputValue | None: ...
|
|
577
|
+
|
|
387
578
|
@overload
|
|
388
579
|
def chat_input(
|
|
389
580
|
self,
|
|
@@ -393,6 +584,8 @@ class ChatMixin:
|
|
|
393
584
|
max_chars: int | None = None,
|
|
394
585
|
accept_file: Literal[True, "multiple", "directory"],
|
|
395
586
|
file_type: str | Sequence[str] | None = None,
|
|
587
|
+
accept_audio: bool = False,
|
|
588
|
+
audio_sample_rate: int | None = 16000,
|
|
396
589
|
disabled: bool = False,
|
|
397
590
|
on_submit: WidgetCallback | None = None,
|
|
398
591
|
args: WidgetArgs | None = None,
|
|
@@ -409,6 +602,8 @@ class ChatMixin:
|
|
|
409
602
|
max_chars: int | None = None,
|
|
410
603
|
accept_file: bool | Literal["multiple", "directory"] = False,
|
|
411
604
|
file_type: str | Sequence[str] | None = None,
|
|
605
|
+
accept_audio: bool = False,
|
|
606
|
+
audio_sample_rate: int | None = 16000,
|
|
412
607
|
disabled: bool = False,
|
|
413
608
|
on_submit: WidgetCallback | None = None,
|
|
414
609
|
args: WidgetArgs | None = None,
|
|
@@ -471,6 +666,23 @@ class ChatMixin:
|
|
|
471
666
|
or type extensions. The correct handling of uploaded files is
|
|
472
667
|
part of the app developer's responsibility.
|
|
473
668
|
|
|
669
|
+
accept_audio : bool
|
|
670
|
+
Whether to show an audio recording button in the chat input. This
|
|
671
|
+
defaults to ``False``. If this is ``True``, users can record and
|
|
672
|
+
submit audio messages. Recorded audio is available as an
|
|
673
|
+
``UploadedFile`` object with MIME type ``audio/wav``.
|
|
674
|
+
|
|
675
|
+
audio_sample_rate : int or None
|
|
676
|
+
The target sample rate for audio recording in Hz when
|
|
677
|
+
``accept_audio`` is ``True``. This defaults to ``16000``, which is
|
|
678
|
+
optimal for speech recognition.
|
|
679
|
+
|
|
680
|
+
The following values are supported: ``8000`` (telephone quality),
|
|
681
|
+
``11025``, ``16000`` (speech-recognition quality), ``22050``,
|
|
682
|
+
``24000``, ``32000``, ``44100``, ``48000`` (high-quality), or
|
|
683
|
+
``None``. If this is ``None``, the widget uses the browser's
|
|
684
|
+
default sample rate (typically 44100 or 48000 Hz).
|
|
685
|
+
|
|
474
686
|
disabled : bool
|
|
475
687
|
Whether the chat input should be disabled. This defaults to
|
|
476
688
|
``False``.
|
|
@@ -500,33 +712,39 @@ class ChatMixin:
|
|
|
500
712
|
None, str, or dict-like
|
|
501
713
|
The user's submission. This is one of the following types:
|
|
502
714
|
|
|
503
|
-
- ``None``: If the user didn't submit a message
|
|
504
|
-
rerun, the widget returns ``None``.
|
|
505
|
-
- A string: When the widget
|
|
506
|
-
the user submitted a message in the last
|
|
507
|
-
returns the user's message as a string.
|
|
715
|
+
- ``None``: If the user didn't submit a message, file, or audio
|
|
716
|
+
recording in the last rerun, the widget returns ``None``.
|
|
717
|
+
- A string: When the widget isn't configured to accept files or
|
|
718
|
+
audio recordings, and the user submitted a message in the last
|
|
719
|
+
rerun, the widget returns the user's message as a string.
|
|
508
720
|
- A dict-like object: When the widget is configured to accept files
|
|
509
|
-
and the user submitted
|
|
510
|
-
rerun, the widget returns a dict-like object
|
|
511
|
-
``text`` and
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
The
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
721
|
+
or audio recordings, and the user submitted any content in the
|
|
722
|
+
last rerun, the widget returns a dict-like object.
|
|
723
|
+
The object always includes the ``text`` attribute, and
|
|
724
|
+
optionally includes ``files`` and/or ``audio`` attributes depending
|
|
725
|
+
on the ``accept_file`` and ``accept_audio`` parameters.
|
|
726
|
+
|
|
727
|
+
When the widget is configured to accept files or audio recordings,
|
|
728
|
+
and the user submitted content in the last rerun, you can access
|
|
729
|
+
the user's submission with key or attribute notation from the
|
|
730
|
+
dict-like object. This is shown in Example 3 below.
|
|
731
|
+
|
|
732
|
+
- The ``text`` attribute holds a string that is the user's message.
|
|
733
|
+
This is an empty string if the user only submitted one or more
|
|
734
|
+
files or audio recordings.
|
|
735
|
+
- The ``files`` attribute is only present when ``accept_file``
|
|
736
|
+
isn't ``False``. When present, it holds a list of
|
|
737
|
+
``UploadedFile`` objects. The list is empty if the user only
|
|
738
|
+
submitted a message or audio recording. Unlike
|
|
739
|
+
``st.file_uploader``, this attribute always returns a list, even
|
|
740
|
+
when the widget is configured to accept only one file at a time.
|
|
741
|
+
- The ``audio`` attribute is only present when ``accept_audio`` is
|
|
742
|
+
``True``. When present, it holds an ``UploadedFile`` object if
|
|
743
|
+
audio was recorded or ``None`` if no audio was recorded.
|
|
744
|
+
|
|
745
|
+
The ``UploadedFile`` class is a subclass of ``BytesIO`` and
|
|
746
|
+
therefore is "file-like". This means you can pass an instance of it
|
|
747
|
+
anywhere a file is expected.
|
|
530
748
|
|
|
531
749
|
Examples
|
|
532
750
|
--------
|
|
@@ -541,7 +759,7 @@ class ChatMixin:
|
|
|
541
759
|
>>> if prompt:
|
|
542
760
|
... st.write(f"User has sent the following prompt: {prompt}")
|
|
543
761
|
|
|
544
|
-
.. output
|
|
762
|
+
.. output::
|
|
545
763
|
https://doc-chat-input.streamlit.app/
|
|
546
764
|
height: 350px
|
|
547
765
|
|
|
@@ -555,12 +773,12 @@ class ChatMixin:
|
|
|
555
773
|
>>> import streamlit as st
|
|
556
774
|
>>>
|
|
557
775
|
>>> with st.sidebar:
|
|
558
|
-
>>> messages = st.container(height=
|
|
776
|
+
>>> messages = st.container(height=200)
|
|
559
777
|
>>> if prompt := st.chat_input("Say something"):
|
|
560
778
|
>>> messages.chat_message("user").write(prompt)
|
|
561
779
|
>>> messages.chat_message("assistant").write(f"Echo: {prompt}")
|
|
562
780
|
|
|
563
|
-
.. output
|
|
781
|
+
.. output::
|
|
564
782
|
https://doc-chat-input-inline.streamlit.app/
|
|
565
783
|
height: 350px
|
|
566
784
|
|
|
@@ -585,7 +803,7 @@ class ChatMixin:
|
|
|
585
803
|
>>> if prompt and prompt["files"]:
|
|
586
804
|
>>> st.image(prompt["files"][0])
|
|
587
805
|
|
|
588
|
-
.. output
|
|
806
|
+
.. output::
|
|
589
807
|
https://doc-chat-input-file-uploader.streamlit.app/
|
|
590
808
|
height: 350px
|
|
591
809
|
|
|
@@ -600,9 +818,32 @@ class ChatMixin:
|
|
|
600
818
|
>>> st.chat_input(key="chat_input")
|
|
601
819
|
>>> st.write("Chat input value:", st.session_state.chat_input)
|
|
602
820
|
|
|
603
|
-
.. output
|
|
821
|
+
.. output::
|
|
604
822
|
https://doc-chat-input-session-state.streamlit.app/
|
|
605
823
|
height: 350px
|
|
824
|
+
|
|
825
|
+
**Example 5: Enable audio recording**
|
|
826
|
+
|
|
827
|
+
You can enable audio recording by setting ``accept_audio=True``.
|
|
828
|
+
The ``accept_audio`` parameter works independently of ``accept_file``,
|
|
829
|
+
allowing you to enable audio recording with or without file uploads.
|
|
830
|
+
|
|
831
|
+
>>> import streamlit as st
|
|
832
|
+
>>>
|
|
833
|
+
>>> prompt = st.chat_input(
|
|
834
|
+
>>> "Say or record something",
|
|
835
|
+
>>> accept_audio=True,
|
|
836
|
+
>>> )
|
|
837
|
+
>>> if prompt and prompt.text:
|
|
838
|
+
>>> st.write("Text:", prompt.text)
|
|
839
|
+
>>> if prompt and prompt.audio:
|
|
840
|
+
>>> st.audio(prompt.audio)
|
|
841
|
+
>>> st.write("Audio file:", prompt.audio.name)
|
|
842
|
+
|
|
843
|
+
.. output::
|
|
844
|
+
https://doc-chat-input-audio.streamlit.app/
|
|
845
|
+
height: 350px
|
|
846
|
+
|
|
606
847
|
"""
|
|
607
848
|
key = to_key(key)
|
|
608
849
|
|
|
@@ -636,12 +877,24 @@ class ChatMixin:
|
|
|
636
877
|
max_chars=max_chars,
|
|
637
878
|
accept_file=accept_file,
|
|
638
879
|
file_type=file_type,
|
|
880
|
+
accept_audio=accept_audio,
|
|
881
|
+
audio_sample_rate=audio_sample_rate,
|
|
639
882
|
width=width,
|
|
640
883
|
)
|
|
641
884
|
|
|
642
885
|
if file_type:
|
|
643
886
|
file_type = normalize_upload_file_type(file_type)
|
|
644
887
|
|
|
888
|
+
# Validate audio_sample_rate if provided
|
|
889
|
+
if (
|
|
890
|
+
audio_sample_rate is not None
|
|
891
|
+
and audio_sample_rate not in ALLOWED_SAMPLE_RATES
|
|
892
|
+
):
|
|
893
|
+
raise StreamlitAPIException(
|
|
894
|
+
f"Invalid audio_sample_rate: {audio_sample_rate}. "
|
|
895
|
+
f"Must be one of {sorted(ALLOWED_SAMPLE_RATES)} Hz, or None for browser default."
|
|
896
|
+
)
|
|
897
|
+
|
|
645
898
|
# It doesn't make sense to create a chat input inside a form.
|
|
646
899
|
# We throw an error to warn the user about this.
|
|
647
900
|
# We omit this check for scripts running outside streamlit, because
|
|
@@ -680,9 +933,14 @@ class ChatMixin:
|
|
|
680
933
|
|
|
681
934
|
chat_input_proto.file_type[:] = file_type if file_type is not None else []
|
|
682
935
|
chat_input_proto.max_upload_size_mb = config.get_option("server.maxUploadSize")
|
|
936
|
+
chat_input_proto.accept_audio = accept_audio
|
|
937
|
+
|
|
938
|
+
if audio_sample_rate is not None:
|
|
939
|
+
chat_input_proto.audio_sample_rate = audio_sample_rate
|
|
683
940
|
|
|
684
941
|
serde = ChatInputSerde(
|
|
685
942
|
accept_files=accept_file in {True, "multiple", "directory"},
|
|
943
|
+
accept_audio=accept_audio,
|
|
686
944
|
allowed_types=file_type,
|
|
687
945
|
)
|
|
688
946
|
widget_state = register_widget( # type: ignore[misc]
|
|
@@ -213,7 +213,14 @@ class ColorPickerMixin:
|
|
|
213
213
|
)
|
|
214
214
|
maybe_raise_label_warnings(label, label_visibility)
|
|
215
215
|
|
|
216
|
+
# Enforce minimum width of 40px to match the color block's intrinsic size.
|
|
217
|
+
# The color block is always 40x40px, so the widget should never be smaller.
|
|
218
|
+
min_width_px = 40
|
|
219
|
+
if isinstance(width, int) and width < min_width_px:
|
|
220
|
+
width = min_width_px
|
|
221
|
+
|
|
216
222
|
validate_width(width, allow_content=True)
|
|
223
|
+
|
|
217
224
|
layout_config = LayoutConfig(width=width)
|
|
218
225
|
|
|
219
226
|
element_id = compute_and_register_element_id(
|