streamlit 1.50.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.
Files changed (406) hide show
  1. streamlit/__init__.py +5 -1
  2. streamlit/commands/execution_control.py +89 -14
  3. streamlit/commands/navigation.py +4 -6
  4. streamlit/commands/page_config.py +4 -6
  5. streamlit/components/v1/component_arrow.py +7 -7
  6. streamlit/components/v2/__init__.py +514 -0
  7. streamlit/components/v2/bidi_component/__init__.py +20 -0
  8. streamlit/components/v2/bidi_component/constants.py +29 -0
  9. streamlit/components/v2/bidi_component/main.py +534 -0
  10. streamlit/components/v2/bidi_component/serialization.py +272 -0
  11. streamlit/components/v2/bidi_component/state.py +92 -0
  12. streamlit/components/v2/component_definition_resolver.py +143 -0
  13. streamlit/components/v2/component_file_watcher.py +403 -0
  14. streamlit/components/v2/component_manager.py +439 -0
  15. streamlit/components/v2/component_manifest_handler.py +122 -0
  16. streamlit/components/v2/component_path_utils.py +245 -0
  17. streamlit/components/v2/component_registry.py +426 -0
  18. streamlit/components/v2/get_bidi_component_manager.py +51 -0
  19. streamlit/components/v2/manifest_scanner.py +615 -0
  20. streamlit/components/v2/presentation.py +198 -0
  21. streamlit/components/v2/types.py +324 -0
  22. streamlit/config.py +456 -53
  23. streamlit/config_option.py +4 -1
  24. streamlit/config_util.py +650 -1
  25. streamlit/connections/snowflake_connection.py +1 -1
  26. streamlit/connections/snowpark_connection.py +1 -1
  27. streamlit/dataframe_util.py +33 -26
  28. streamlit/delta_generator.py +13 -4
  29. streamlit/delta_generator_singletons.py +11 -15
  30. streamlit/deprecation_util.py +17 -6
  31. streamlit/elements/alert.py +16 -0
  32. streamlit/elements/arrow.py +68 -10
  33. streamlit/elements/bokeh_chart.py +10 -78
  34. streamlit/elements/code.py +2 -2
  35. streamlit/elements/deck_gl_json_chart.py +98 -40
  36. streamlit/elements/dialog_decorator.py +2 -1
  37. streamlit/elements/exception.py +4 -2
  38. streamlit/elements/form.py +27 -0
  39. streamlit/elements/graphviz_chart.py +1 -3
  40. streamlit/elements/heading.py +63 -10
  41. streamlit/elements/html.py +13 -2
  42. streamlit/elements/image.py +3 -5
  43. streamlit/elements/layouts.py +59 -33
  44. streamlit/elements/lib/built_in_chart_utils.py +50 -19
  45. streamlit/elements/lib/color_util.py +9 -19
  46. streamlit/elements/lib/column_config_utils.py +9 -12
  47. streamlit/elements/lib/column_types.py +40 -12
  48. streamlit/elements/lib/dialog.py +2 -2
  49. streamlit/elements/lib/image_utils.py +3 -5
  50. streamlit/elements/lib/layout_utils.py +100 -13
  51. streamlit/elements/lib/mutable_status_container.py +2 -2
  52. streamlit/elements/lib/options_selector_utils.py +2 -2
  53. streamlit/elements/lib/pandas_styler_utils.py +17 -9
  54. streamlit/elements/lib/shortcut_utils.py +152 -0
  55. streamlit/elements/lib/utils.py +4 -4
  56. streamlit/elements/map.py +80 -37
  57. streamlit/elements/markdown.py +50 -3
  58. streamlit/elements/media.py +5 -7
  59. streamlit/elements/metric.py +34 -6
  60. streamlit/elements/pdf.py +2 -4
  61. streamlit/elements/plotly_chart.py +197 -20
  62. streamlit/elements/progress.py +2 -4
  63. streamlit/elements/space.py +113 -0
  64. streamlit/elements/spinner.py +1 -1
  65. streamlit/elements/text.py +20 -3
  66. streamlit/elements/toast.py +2 -0
  67. streamlit/elements/vega_charts.py +356 -149
  68. streamlit/elements/widgets/audio_input.py +12 -11
  69. streamlit/elements/widgets/button.py +280 -43
  70. streamlit/elements/widgets/button_group.py +60 -9
  71. streamlit/elements/widgets/camera_input.py +3 -5
  72. streamlit/elements/widgets/chat.py +307 -43
  73. streamlit/elements/widgets/color_picker.py +8 -1
  74. streamlit/elements/widgets/data_editor.py +88 -44
  75. streamlit/elements/widgets/file_uploader.py +9 -11
  76. streamlit/elements/widgets/multiselect.py +4 -3
  77. streamlit/elements/widgets/number_input.py +4 -4
  78. streamlit/elements/widgets/radio.py +10 -3
  79. streamlit/elements/widgets/select_slider.py +8 -5
  80. streamlit/elements/widgets/selectbox.py +6 -3
  81. streamlit/elements/widgets/slider.py +38 -42
  82. streamlit/elements/widgets/text_widgets.py +2 -0
  83. streamlit/elements/widgets/time_widgets.py +587 -21
  84. streamlit/elements/write.py +27 -6
  85. streamlit/emojis.py +1 -1
  86. streamlit/errors.py +137 -0
  87. streamlit/git_util.py +1 -1
  88. streamlit/hello/hello.py +8 -0
  89. streamlit/hello/utils.py +2 -1
  90. streamlit/material_icon_names.py +1 -1
  91. streamlit/navigation/page.py +11 -1
  92. streamlit/net_util.py +2 -2
  93. streamlit/proto/Alert_pb2.pyi +3 -3
  94. streamlit/proto/AppPage_pb2.pyi +7 -1
  95. streamlit/proto/ArrowData_pb2.py +27 -0
  96. streamlit/proto/ArrowData_pb2.pyi +52 -0
  97. streamlit/proto/ArrowNamedDataSet_pb2.pyi +7 -1
  98. streamlit/proto/ArrowVegaLiteChart_pb2.pyi +7 -1
  99. streamlit/proto/Arrow_pb2.py +10 -10
  100. streamlit/proto/Arrow_pb2.pyi +19 -12
  101. streamlit/proto/AudioInput_pb2.pyi +7 -1
  102. streamlit/proto/Audio_pb2.pyi +7 -1
  103. streamlit/proto/AuthRedirect_pb2.pyi +7 -1
  104. streamlit/proto/AutoRerun_pb2.pyi +7 -1
  105. streamlit/proto/BackMsg_pb2.py +4 -2
  106. streamlit/proto/BackMsg_pb2.pyi +34 -4
  107. streamlit/proto/Balloons_pb2.pyi +7 -1
  108. streamlit/proto/BidiComponent_pb2.py +34 -0
  109. streamlit/proto/BidiComponent_pb2.pyi +159 -0
  110. streamlit/proto/Block_pb2.py +7 -7
  111. streamlit/proto/Block_pb2.pyi +39 -36
  112. streamlit/proto/BokehChart_pb2.pyi +7 -1
  113. streamlit/proto/ButtonGroup_pb2.pyi +9 -9
  114. streamlit/proto/Button_pb2.py +2 -2
  115. streamlit/proto/Button_pb2.pyi +11 -2
  116. streamlit/proto/CameraInput_pb2.pyi +7 -1
  117. streamlit/proto/ChatInput_pb2.py +6 -6
  118. streamlit/proto/ChatInput_pb2.pyi +18 -6
  119. streamlit/proto/Checkbox_pb2.pyi +3 -3
  120. streamlit/proto/ClientState_pb2.pyi +10 -4
  121. streamlit/proto/Code_pb2.pyi +7 -1
  122. streamlit/proto/ColorPicker_pb2.pyi +7 -1
  123. streamlit/proto/Common_pb2.py +3 -3
  124. streamlit/proto/Common_pb2.pyi +35 -23
  125. streamlit/proto/Components_pb2.pyi +19 -13
  126. streamlit/proto/DataFrame_pb2.pyi +55 -49
  127. streamlit/proto/DateInput_pb2.pyi +7 -1
  128. streamlit/proto/DateTimeInput_pb2.py +28 -0
  129. streamlit/proto/DateTimeInput_pb2.pyi +92 -0
  130. streamlit/proto/DeckGlJsonChart_pb2.py +10 -4
  131. streamlit/proto/DeckGlJsonChart_pb2.pyi +12 -6
  132. streamlit/proto/Delta_pb2.pyi +7 -1
  133. streamlit/proto/DocString_pb2.pyi +10 -4
  134. streamlit/proto/DownloadButton_pb2.py +2 -2
  135. streamlit/proto/DownloadButton_pb2.pyi +16 -2
  136. streamlit/proto/Element_pb2.py +7 -3
  137. streamlit/proto/Element_pb2.pyi +33 -5
  138. streamlit/proto/Empty_pb2.pyi +7 -1
  139. streamlit/proto/Exception_pb2.pyi +7 -1
  140. streamlit/proto/Favicon_pb2.pyi +7 -1
  141. streamlit/proto/FileUploader_pb2.pyi +7 -1
  142. streamlit/proto/ForwardMsg_pb2.py +12 -10
  143. streamlit/proto/ForwardMsg_pb2.pyi +42 -15
  144. streamlit/proto/GapSize_pb2.pyi +4 -4
  145. streamlit/proto/GitInfo_pb2.pyi +3 -3
  146. streamlit/proto/GraphVizChart_pb2.pyi +7 -1
  147. streamlit/proto/Heading_pb2.pyi +7 -1
  148. streamlit/proto/HeightConfig_pb2.py +2 -2
  149. streamlit/proto/HeightConfig_pb2.pyi +13 -4
  150. streamlit/proto/Html_pb2.py +2 -2
  151. streamlit/proto/Html_pb2.pyi +11 -2
  152. streamlit/proto/IFrame_pb2.pyi +7 -1
  153. streamlit/proto/Image_pb2.pyi +10 -4
  154. streamlit/proto/Json_pb2.pyi +7 -1
  155. streamlit/proto/LabelVisibilityMessage_pb2.pyi +3 -3
  156. streamlit/proto/LinkButton_pb2.py +2 -2
  157. streamlit/proto/LinkButton_pb2.pyi +15 -2
  158. streamlit/proto/Logo_pb2.pyi +7 -1
  159. streamlit/proto/Markdown_pb2.pyi +3 -3
  160. streamlit/proto/Metric_pb2.pyi +7 -7
  161. streamlit/proto/MetricsEvent_pb2.pyi +10 -4
  162. streamlit/proto/MultiSelect_pb2.pyi +7 -1
  163. streamlit/proto/NamedDataSet_pb2.pyi +7 -1
  164. streamlit/proto/Navigation_pb2.pyi +3 -3
  165. streamlit/proto/NewSession_pb2.py +18 -18
  166. streamlit/proto/NewSession_pb2.pyi +59 -40
  167. streamlit/proto/NumberInput_pb2.pyi +3 -3
  168. streamlit/proto/PageConfig_pb2.pyi +7 -7
  169. streamlit/proto/PageInfo_pb2.pyi +7 -1
  170. streamlit/proto/PageLink_pb2.py +2 -2
  171. streamlit/proto/PageLink_pb2.pyi +11 -2
  172. streamlit/proto/PageNotFound_pb2.pyi +7 -1
  173. streamlit/proto/PageProfile_pb2.pyi +13 -7
  174. streamlit/proto/PagesChanged_pb2.pyi +7 -1
  175. streamlit/proto/ParentMessage_pb2.pyi +7 -1
  176. streamlit/proto/PlotlyChart_pb2.py +8 -6
  177. streamlit/proto/PlotlyChart_pb2.pyi +9 -7
  178. streamlit/proto/Progress_pb2.pyi +7 -1
  179. streamlit/proto/Radio_pb2.pyi +7 -1
  180. streamlit/proto/RootContainer_pb2.pyi +1 -1
  181. streamlit/proto/Selectbox_pb2.pyi +7 -1
  182. streamlit/proto/SessionEvent_pb2.pyi +7 -1
  183. streamlit/proto/SessionStatus_pb2.pyi +7 -1
  184. streamlit/proto/Skeleton_pb2.pyi +3 -3
  185. streamlit/proto/Slider_pb2.pyi +5 -5
  186. streamlit/proto/Snow_pb2.pyi +7 -1
  187. streamlit/proto/Space_pb2.py +27 -0
  188. streamlit/proto/Space_pb2.pyi +48 -0
  189. streamlit/proto/Spinner_pb2.pyi +7 -1
  190. streamlit/proto/TextAlignmentConfig_pb2.py +29 -0
  191. streamlit/proto/TextAlignmentConfig_pb2.pyi +68 -0
  192. streamlit/proto/TextArea_pb2.pyi +7 -1
  193. streamlit/proto/TextInput_pb2.pyi +3 -3
  194. streamlit/proto/Text_pb2.pyi +7 -1
  195. streamlit/proto/TimeInput_pb2.pyi +7 -1
  196. streamlit/proto/Toast_pb2.pyi +7 -1
  197. streamlit/proto/VegaLiteChart_pb2.pyi +7 -1
  198. streamlit/proto/Video_pb2.pyi +6 -6
  199. streamlit/proto/WidgetStates_pb2.py +2 -2
  200. streamlit/proto/WidgetStates_pb2.pyi +23 -7
  201. streamlit/proto/WidthConfig_pb2.py +2 -2
  202. streamlit/proto/WidthConfig_pb2.pyi +13 -4
  203. streamlit/proto/openmetrics_data_model_pb2.pyi +52 -52
  204. streamlit/runtime/app_session.py +65 -2
  205. streamlit/runtime/caching/cache_data_api.py +5 -5
  206. streamlit/runtime/caching/cache_errors.py +4 -1
  207. streamlit/runtime/caching/cache_resource_api.py +5 -4
  208. streamlit/runtime/caching/cache_utils.py +3 -2
  209. streamlit/runtime/caching/cached_message_replay.py +3 -3
  210. streamlit/runtime/caching/hashing.py +4 -5
  211. streamlit/runtime/caching/legacy_cache_api.py +2 -1
  212. streamlit/runtime/connection_factory.py +1 -3
  213. streamlit/runtime/download_data_util.py +53 -0
  214. streamlit/runtime/forward_msg_queue.py +5 -1
  215. streamlit/runtime/fragment.py +2 -1
  216. streamlit/runtime/media_file_manager.py +178 -2
  217. streamlit/runtime/memory_media_file_storage.py +1 -1
  218. streamlit/runtime/metrics_util.py +91 -3
  219. streamlit/runtime/runtime.py +14 -0
  220. streamlit/runtime/scriptrunner/exec_code.py +2 -1
  221. streamlit/runtime/scriptrunner/script_runner.py +5 -3
  222. streamlit/runtime/scriptrunner_utils/script_run_context.py +3 -6
  223. streamlit/runtime/secrets.py +2 -4
  224. streamlit/runtime/session_manager.py +3 -1
  225. streamlit/runtime/state/common.py +30 -5
  226. streamlit/runtime/state/presentation.py +85 -0
  227. streamlit/runtime/state/query_params.py +80 -29
  228. streamlit/runtime/state/safe_session_state.py +2 -2
  229. streamlit/runtime/state/session_state.py +221 -17
  230. streamlit/runtime/state/widgets.py +19 -3
  231. streamlit/runtime/websocket_session_manager.py +3 -1
  232. streamlit/source_util.py +2 -2
  233. streamlit/static/index.html +2 -2
  234. streamlit/static/manifest.json +557 -239
  235. streamlit/static/static/css/{index.CIiu7Ygf.css → index.BpABIXK9.css} +1 -1
  236. streamlit/static/static/css/index.DgR7E2CV.css +1 -0
  237. streamlit/static/static/js/{ErrorOutline.esm.DUpR0_Ka.js → ErrorOutline.esm.ZJDbmVTx.js} +1 -1
  238. streamlit/static/static/js/{FileDownload.esm.CN4j9-1w.js → FileDownload.esm.Dx0vI3vH.js} +1 -1
  239. streamlit/static/static/js/{FileHelper.CaIUKG91.js → FileHelper.B7Ero7qQ.js} +3 -3
  240. streamlit/static/static/js/{FormClearHelper.DTcdrasw.js → FormClearHelper.CG2XN1_g.js} +1 -1
  241. streamlit/static/static/js/IFrameUtil.DefezniK.js +1 -0
  242. streamlit/static/static/js/InputInstructions.Cj5-1zf6.js +1 -0
  243. streamlit/static/static/js/Particles.BfWfv0Aw.js +1 -0
  244. streamlit/static/static/js/{ProgressBar.DetlP5aY.js → ProgressBar.CGQ8OgfO.js} +2 -2
  245. streamlit/static/static/js/StreamlitSyntaxHighlighter.DTKLpwhl.js +20 -0
  246. streamlit/static/static/js/{Toolbar.C77ar7rq.js → Toolbar.B2qFUmd9.js} +1 -1
  247. streamlit/static/static/js/_arrayIncludes.B19Iyn2B.js +1 -0
  248. streamlit/static/static/js/_baseIndexOf.BTknn6Gb.js +1 -0
  249. streamlit/static/static/js/{base-input.BQft14La.js → base-input.o9tL8MDP.js} +4 -4
  250. streamlit/static/static/js/{checkbox.yZOfXCeX.js → checkbox.0BeV1IBL.js} +1 -1
  251. streamlit/static/static/js/{createSuper.Dh9w1cs8.js → createSuper.RBO59fEm.js} +1 -1
  252. streamlit/static/static/js/data-grid-overlay-editor.CiTkUy0t.js +1 -0
  253. streamlit/static/static/js/{downloader.MeHtkq8r.js → downloader.DwNZg3Mw.js} +1 -1
  254. streamlit/static/static/js/embed.XT9xNd3F.js +195 -0
  255. streamlit/static/static/js/{es6.VpBPGCnM.js → es6.x9KsYQg-.js} +2 -2
  256. streamlit/static/static/js/{iframeResizer.contentWindow.yMw_ARIL.js → iframeResizer.contentWindow.ZVXpMPi0.js} +1 -1
  257. streamlit/static/static/js/index.5VPOamri.js +1 -0
  258. streamlit/static/static/js/index.8HslT92O.js +14 -0
  259. streamlit/static/static/js/index.AnXMIBz3.js +7 -0
  260. streamlit/static/static/js/index.B0yp3bM1.js +6 -0
  261. streamlit/static/static/js/index.B1fRb5wF.js +1 -0
  262. streamlit/static/static/js/index.B527JZdO.js +3 -0
  263. streamlit/static/static/js/index.BHgV-yW4.js +1 -0
  264. streamlit/static/static/js/index.BQr-XwGV.js +1 -0
  265. streamlit/static/static/js/index.BTtmaLDB.js +1 -0
  266. streamlit/static/static/js/index.BWB_91TA.js +1 -0
  267. streamlit/static/static/js/index.BfEKaEmw.js +1 -0
  268. streamlit/static/static/js/index.BfXjTO8b.js +1 -0
  269. streamlit/static/static/js/index.Bjy4NRu9.js +3 -0
  270. streamlit/static/static/js/index.Bu5JWpT_.js +1 -0
  271. streamlit/static/static/js/index.BuCx76ZV.js +1 -0
  272. streamlit/static/static/js/index.BxjzhVUb.js +2 -0
  273. streamlit/static/static/js/index.By55VdPY.js +1 -0
  274. streamlit/static/static/js/index.CF5MxTbK.js +1 -0
  275. streamlit/static/static/js/index.CLmq_z9K.js +1 -0
  276. streamlit/static/static/js/index.CNH4rdSz.js +1 -0
  277. streamlit/static/static/js/{index.B0H9IXUJ.js → index.CTgm_-jO.js} +10 -41
  278. streamlit/static/static/js/index.C_rK-Swb.js +188 -0
  279. streamlit/static/static/js/index.CjozwSzS.js +1 -0
  280. streamlit/static/static/js/{index.CH1tqnSs.js → index.CkGVt6-G.js} +1 -1
  281. streamlit/static/static/js/index.CuvXOyER.js +2 -0
  282. streamlit/static/static/js/{index.FFOzOWzC.js → index.CyUHWoCC.js} +2 -2
  283. streamlit/static/static/js/index.CyroQtI4.js +2 -0
  284. streamlit/static/static/js/index.D6HmkoDm.js +263 -0
  285. streamlit/static/static/js/index.DAqCNvsO.js +1 -0
  286. streamlit/static/static/js/index.DB_w_CZQ.js +1 -0
  287. streamlit/static/static/js/index.DBalctjj.js +2 -0
  288. streamlit/static/static/js/index.DK0RFJUG.js +11 -0
  289. streamlit/static/static/js/index.DMxc2XFp.js +151 -0
  290. streamlit/static/static/js/index.DO5utP74.js +2 -0
  291. streamlit/static/static/js/index.DS7lf09n.js +1 -0
  292. streamlit/static/static/js/index.DWexTVLY.js +1 -0
  293. streamlit/static/static/js/index.DXxnU5ej.js +1 -0
  294. streamlit/static/static/js/index.DcU3uDvB.js +2 -0
  295. streamlit/static/static/js/index.DlltaH7J.js +1 -0
  296. streamlit/static/static/js/index.DpNTZz82.js +27 -0
  297. streamlit/static/static/js/index.Dr9HIhQw.js +1 -0
  298. streamlit/static/static/js/index.DsgAU5lc.js +1 -0
  299. streamlit/static/static/js/{index.64ejlaaT.js → index.KfXqjDYy.js} +1 -1
  300. streamlit/static/static/js/index.PaidgjCs.js +1 -0
  301. streamlit/static/static/js/index.RJZuWCGA.js +1 -0
  302. streamlit/static/static/js/{index.Ctn27_AE.js → index.hbeqcRTn.js} +53 -122
  303. streamlit/static/static/js/index.q5hIQwAY.js +1 -0
  304. streamlit/static/static/js/index.rORSX6IW.js +1 -0
  305. streamlit/static/static/js/index.uSX757_v.js +1 -0
  306. streamlit/static/static/js/index.x_QRaLMd.js +1 -0
  307. streamlit/static/static/js/{input.s6pjQ49A.js → input.D5oh9-aB.js} +2 -2
  308. streamlit/static/static/js/main.q9oGOg0H.js +13 -0
  309. streamlit/static/static/js/{memory.Cuvsdfrl.js → memory.5kCSFUJS.js} +1 -1
  310. streamlit/static/static/js/moment.C3j7ZXd7.js +4 -0
  311. streamlit/static/static/js/number-overlay-editor.Cn_LsK8N.js +9 -0
  312. streamlit/static/static/js/pandasStylerUtils.BqhXt51_.js +1 -0
  313. streamlit/static/static/js/{possibleConstructorReturn.CqidKeei.js → possibleConstructorReturn.DD9NK1Z8.js} +1 -1
  314. streamlit/static/static/js/record.B-tDciZb.js +1 -0
  315. streamlit/static/static/js/{sandbox.CCQREcJx.js → sandbox.DACSyz29.js} +1 -1
  316. streamlit/static/static/js/styled-components.C3R090At.js +1 -0
  317. streamlit/static/static/js/threshold.Q1mXg5rX.js +1 -0
  318. streamlit/static/static/js/throttle.B0GR3Iyz.js +1 -0
  319. streamlit/static/static/js/{timepicker.mkJF97Bb.js → timepicker.BdhzPxrv.js} +1 -1
  320. streamlit/static/static/js/timer.C2hYhUse.js +1 -0
  321. streamlit/static/static/js/{toConsumableArray.De7I7KVR.js → toConsumableArray.Db2pdqM2.js} +1 -1
  322. streamlit/static/static/js/uniqueId.CtqIr-Yh.js +1 -0
  323. streamlit/static/static/js/urls.BwSlolu9.js +1 -0
  324. streamlit/static/static/js/{useBasicWidgetState.CedkNjUW.js → useBasicWidgetState.Bfp6TnSw.js} +1 -1
  325. streamlit/static/static/js/useIntlLocale.hRV75Xgj.js +12 -0
  326. streamlit/static/static/js/{useTextInputAutoExpand.Ca7w8dVs.js → useTextInputAutoExpand.QepX7n8Y.js} +1 -1
  327. streamlit/static/static/js/useUpdateUiValue.DHx8TzX6.js +1 -0
  328. streamlit/static/static/js/useWaveformController.WxVzpzEX.js +1 -0
  329. streamlit/static/static/js/value.B4vHRSi7.js +1 -0
  330. streamlit/static/static/js/wavesurfer.esm.vI8Eid4k.js +73 -0
  331. streamlit/static/static/js/withCalculatedWidth.DcKeRSWJ.js +1 -0
  332. streamlit/static/static/js/withFullScreenWrapper.CrHddARq.js +1 -0
  333. streamlit/static/static/media/MaterialSymbols-Rounded.C7IFxh57.woff2 +0 -0
  334. streamlit/string_util.py +9 -4
  335. streamlit/testing/v1/app_test.py +17 -2
  336. streamlit/testing/v1/element_tree.py +85 -9
  337. streamlit/testing/v1/util.py +2 -2
  338. streamlit/type_util.py +3 -4
  339. streamlit/url_util.py +1 -3
  340. streamlit/user_info.py +1 -2
  341. streamlit/util.py +3 -1
  342. streamlit/watcher/event_based_path_watcher.py +23 -12
  343. streamlit/watcher/local_sources_watcher.py +11 -1
  344. streamlit/watcher/path_watcher.py +9 -6
  345. streamlit/watcher/polling_path_watcher.py +4 -1
  346. streamlit/watcher/util.py +2 -2
  347. streamlit/web/bootstrap.py +24 -0
  348. streamlit/web/cli.py +51 -22
  349. streamlit/web/server/bidi_component_request_handler.py +193 -0
  350. streamlit/web/server/component_file_utils.py +97 -0
  351. streamlit/web/server/component_request_handler.py +8 -21
  352. streamlit/web/server/oauth_authlib_routes.py +5 -2
  353. streamlit/web/server/oidc_mixin.py +3 -1
  354. streamlit/web/server/routes.py +2 -2
  355. streamlit/web/server/server.py +9 -0
  356. streamlit/web/server/server_util.py +3 -1
  357. streamlit/web/server/upload_file_request_handler.py +19 -1
  358. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/METADATA +10 -7
  359. streamlit-1.52.0.dist-info/RECORD +620 -0
  360. streamlit/static/static/css/index.CHEnSPGk.css +0 -1
  361. streamlit/static/static/js/Hooks.BRba_Own.js +0 -1
  362. streamlit/static/static/js/InputInstructions.xnSDuYeQ.js +0 -1
  363. streamlit/static/static/js/Particles.CElH0XX2.js +0 -1
  364. streamlit/static/static/js/data-grid-overlay-editor.DcuHuCyW.js +0 -1
  365. streamlit/static/static/js/index.6xX1278W.js +0 -975
  366. streamlit/static/static/js/index.B-hiXRzw.js +0 -1
  367. streamlit/static/static/js/index.B4cAbHP6.js +0 -1
  368. streamlit/static/static/js/index.B4dUQfni.js +0 -1
  369. streamlit/static/static/js/index.BPQo7BKk.js +0 -1
  370. streamlit/static/static/js/index.Baqa90pe.js +0 -2
  371. streamlit/static/static/js/index.Bj9JgOEC.js +0 -1
  372. streamlit/static/static/js/index.BjCwMzj4.js +0 -3
  373. streamlit/static/static/js/index.Bm3VbPB5.js +0 -1
  374. streamlit/static/static/js/index.Bxz2yX3P.js +0 -1
  375. streamlit/static/static/js/index.BycLveZ4.js +0 -1
  376. streamlit/static/static/js/index.C9BdUqTi.js +0 -1
  377. streamlit/static/static/js/index.CFMf5_ez.js +0 -197
  378. streamlit/static/static/js/index.CGYqqs6j.js +0 -1
  379. streamlit/static/static/js/index.CMItVsFA.js +0 -1
  380. streamlit/static/static/js/index.CTBk8Vk2.js +0 -1
  381. streamlit/static/static/js/index.CiAQIz1H.js +0 -7
  382. streamlit/static/static/js/index.Cj7DSzVR.js +0 -73
  383. streamlit/static/static/js/index.Ck8rQ9OL.js +0 -1
  384. streamlit/static/static/js/index.ClELlchS.js +0 -1617
  385. streamlit/static/static/js/index.Cnpi3o3E.js +0 -1
  386. streamlit/static/static/js/index.D2QEXQq_.js +0 -1
  387. streamlit/static/static/js/index.DH71Ezyj.js +0 -1
  388. streamlit/static/static/js/index.DHh-U0dK.js +0 -3
  389. streamlit/static/static/js/index.DK7hD7_w.js +0 -1
  390. streamlit/static/static/js/index.DKv_lNO7.js +0 -2
  391. streamlit/static/static/js/index.DNLrMXgm.js +0 -12
  392. streamlit/static/static/js/index.DW0Grddz.js +0 -1
  393. streamlit/static/static/js/index.Dbe-Q3C-.js +0 -2
  394. streamlit/static/static/js/index.DcPNYEUo.js +0 -1
  395. streamlit/static/static/js/index.DuxqVQpd.js +0 -1
  396. streamlit/static/static/js/index.GRUzrudl.js +0 -1
  397. streamlit/static/static/js/number-overlay-editor.DdgVR5m3.js +0 -9
  398. streamlit/static/static/js/uniqueId.RI1LJdtz.js +0 -1
  399. streamlit/static/static/js/useUpdateUiValue.DeXelfRH.js +0 -1
  400. streamlit/static/static/js/withFullScreenWrapper.C3561XxJ.js +0 -1
  401. streamlit/static/static/media/MaterialSymbols-Rounded.DeCZgS-4.woff2 +0 -0
  402. streamlit-1.50.0.dist-info/RECORD +0 -557
  403. {streamlit-1.50.0.data → streamlit-1.52.0.data}/scripts/streamlit.cmd +0 -0
  404. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/WHEEL +0 -0
  405. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/entry_points.txt +0 -0
  406. {streamlit-1.50.0.dist-info → streamlit-1.52.0.dist-info}/top_level.txt +0 -0
@@ -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(vars(self))
135
+ return len(self._get_included_keys())
80
136
 
81
137
  def __iter__(self) -> Iterator[str]:
82
- return iter(vars(self))
138
+ return iter(self._get_included_keys())
83
139
 
84
- def __getitem__(self, item: str) -> str | list[UploadedFile]:
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
- return vars(self)
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
- if hasattr(ctx.uploaded_file_mgr, "remove_file"):
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 or file in the last
504
- rerun, the widget returns ``None``.
505
- - A string: When the widget is not configured to accept files and
506
- the user submitted a message in the last rerun, the widget
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 a message and/or file(s) in the last
510
- rerun, the widget returns a dict-like object with two attributes,
511
- ``text`` and ``files``.
512
-
513
- When the widget is configured to accept files and the user submits
514
- something in the last rerun, you can access the user's submission
515
- with key or attribute notation from the dict-like object. This is
516
- shown in Example 3 below.
517
-
518
- The ``text`` attribute holds a string, which is the user's message.
519
- This is an empty string if the user only submitted one or more
520
- files.
521
-
522
- The ``files`` attribute holds a list of UploadedFile objects.
523
- The list is empty if the user only submitted a message. Unlike
524
- ``st.file_uploader``, this attribute always returns a list, even
525
- when the widget is configured to accept only one file at a time.
526
-
527
- The UploadedFile class is a subclass of BytesIO, and therefore is
528
- "file-like". This means you can pass an instance of it anywhere a
529
- file is expected.
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=300)
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
 
@@ -624,18 +865,36 @@ class ChatMixin:
624
865
  element_id = compute_and_register_element_id(
625
866
  "chat_input",
626
867
  user_key=key,
627
- key_as_main_identity=False,
868
+ # Treat the provided key as the main identity. Only include
869
+ # properties that can invalidate the current widget state
870
+ # when changed. For chat_input, those are:
871
+ # - accept_file: Changes whether files can be attached (and how)
872
+ # - file_type: Restricts the accepted file types
873
+ # - max_chars: Changes the maximum allowed characters for the input
874
+ key_as_main_identity={"accept_file", "file_type", "max_chars"},
628
875
  dg=self.dg,
629
876
  placeholder=placeholder,
630
877
  max_chars=max_chars,
631
878
  accept_file=accept_file,
632
879
  file_type=file_type,
880
+ accept_audio=accept_audio,
881
+ audio_sample_rate=audio_sample_rate,
633
882
  width=width,
634
883
  )
635
884
 
636
885
  if file_type:
637
886
  file_type = normalize_upload_file_type(file_type)
638
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
+
639
898
  # It doesn't make sense to create a chat input inside a form.
640
899
  # We throw an error to warn the user about this.
641
900
  # We omit this check for scripts running outside streamlit, because
@@ -674,9 +933,14 @@ class ChatMixin:
674
933
 
675
934
  chat_input_proto.file_type[:] = file_type if file_type is not None else []
676
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
677
940
 
678
941
  serde = ChatInputSerde(
679
942
  accept_files=accept_file in {True, "multiple", "directory"},
943
+ accept_audio=accept_audio,
680
944
  allowed_types=file_type,
681
945
  )
682
946
  widget_state = register_widget( # type: ignore[misc]
@@ -213,13 +213,20 @@ 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(
220
227
  "color_picker",
221
228
  user_key=key,
222
- key_as_main_identity=False,
229
+ key_as_main_identity=True,
223
230
  dg=self.dg,
224
231
  label=label,
225
232
  value=str(value),