chainlit 2.0.0__py3-none-any.whl → 2.0.dev0__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.

Potentially problematic release.


This version of chainlit might be problematic. Click here for more details.

Files changed (98) hide show
  1. chainlit/__init__.py +57 -56
  2. chainlit/action.py +10 -12
  3. chainlit/{auth/__init__.py → auth.py} +34 -26
  4. chainlit/cache.py +6 -4
  5. chainlit/callbacks.py +7 -52
  6. chainlit/chat_context.py +2 -2
  7. chainlit/chat_settings.py +1 -3
  8. chainlit/cli/__init__.py +2 -15
  9. chainlit/config.py +70 -41
  10. chainlit/context.py +9 -8
  11. chainlit/copilot/dist/index.js +874 -8533
  12. chainlit/data/__init__.py +8 -96
  13. chainlit/data/acl.py +2 -3
  14. chainlit/data/base.py +15 -1
  15. chainlit/data/dynamodb.py +4 -7
  16. chainlit/data/literalai.py +6 -4
  17. chainlit/data/sql_alchemy.py +9 -10
  18. chainlit/data/{storage_clients/azure.py → storage_clients.py} +33 -2
  19. chainlit/discord/__init__.py +4 -4
  20. chainlit/discord/app.py +1 -2
  21. chainlit/element.py +9 -41
  22. chainlit/emitter.py +21 -17
  23. chainlit/frontend/dist/assets/DailyMotion-b4b7af47.js +1 -0
  24. chainlit/frontend/dist/assets/Facebook-572972a0.js +1 -0
  25. chainlit/frontend/dist/assets/FilePlayer-85c69ca8.js +1 -0
  26. chainlit/frontend/dist/assets/Kaltura-dfc24672.js +1 -0
  27. chainlit/frontend/dist/assets/Mixcloud-705011f4.js +1 -0
  28. chainlit/frontend/dist/assets/Mux-4201a9e6.js +1 -0
  29. chainlit/frontend/dist/assets/Preview-23ba40a6.js +1 -0
  30. chainlit/frontend/dist/assets/SoundCloud-1a582d51.js +1 -0
  31. chainlit/frontend/dist/assets/Streamable-5017c4ba.js +1 -0
  32. chainlit/frontend/dist/assets/Twitch-bb2de2fa.js +1 -0
  33. chainlit/frontend/dist/assets/Vidyard-54e269b1.js +1 -0
  34. chainlit/frontend/dist/assets/Vimeo-d92c37dd.js +1 -0
  35. chainlit/frontend/dist/assets/Wistia-25a1363b.js +1 -0
  36. chainlit/frontend/dist/assets/YouTube-616e8cb7.js +1 -0
  37. chainlit/frontend/dist/assets/index-aaf974a9.css +1 -0
  38. chainlit/frontend/dist/assets/index-f5df2072.js +1027 -0
  39. chainlit/frontend/dist/assets/{react-plotly-BpxUS-ab.js → react-plotly-f0315f86.js} +94 -94
  40. chainlit/frontend/dist/index.html +3 -2
  41. chainlit/haystack/callbacks.py +4 -5
  42. chainlit/input_widget.py +4 -6
  43. chainlit/langchain/callbacks.py +47 -56
  44. chainlit/langflow/__init__.py +0 -1
  45. chainlit/llama_index/callbacks.py +7 -7
  46. chainlit/message.py +10 -8
  47. chainlit/mistralai/__init__.py +2 -3
  48. chainlit/oauth_providers.py +12 -113
  49. chainlit/openai/__init__.py +7 -6
  50. chainlit/secret.py +1 -1
  51. chainlit/server.py +181 -491
  52. chainlit/session.py +5 -7
  53. chainlit/slack/__init__.py +3 -3
  54. chainlit/slack/app.py +2 -3
  55. chainlit/socket.py +103 -78
  56. chainlit/step.py +29 -21
  57. chainlit/sync.py +1 -2
  58. chainlit/teams/__init__.py +3 -3
  59. chainlit/teams/app.py +0 -1
  60. chainlit/types.py +4 -20
  61. chainlit/user.py +1 -2
  62. chainlit/utils.py +2 -3
  63. chainlit/version.py +2 -3
  64. {chainlit-2.0.0.dist-info → chainlit-2.0.dev0.dist-info}/METADATA +39 -27
  65. chainlit-2.0.dev0.dist-info/RECORD +96 -0
  66. chainlit/auth/cookie.py +0 -123
  67. chainlit/auth/jwt.py +0 -37
  68. chainlit/data/chainlit_data_layer.py +0 -584
  69. chainlit/data/storage_clients/__init__.py +0 -0
  70. chainlit/data/storage_clients/azure_blob.py +0 -80
  71. chainlit/data/storage_clients/base.py +0 -22
  72. chainlit/data/storage_clients/gcs.py +0 -78
  73. chainlit/data/storage_clients/s3.py +0 -49
  74. chainlit/frontend/dist/assets/DailyMotion-DgRzV5GZ.js +0 -1
  75. chainlit/frontend/dist/assets/Dataframe-DVgwSMU2.js +0 -22
  76. chainlit/frontend/dist/assets/Facebook-C0vx6HWv.js +0 -1
  77. chainlit/frontend/dist/assets/FilePlayer-CdhzeHPP.js +0 -1
  78. chainlit/frontend/dist/assets/Kaltura-5iVmeUct.js +0 -1
  79. chainlit/frontend/dist/assets/Mixcloud-C2zi77Ex.js +0 -1
  80. chainlit/frontend/dist/assets/Mux-Vkebogdf.js +0 -1
  81. chainlit/frontend/dist/assets/Preview-DwY_sEIl.js +0 -1
  82. chainlit/frontend/dist/assets/SoundCloud-CREBXAWo.js +0 -1
  83. chainlit/frontend/dist/assets/Streamable-B5Lu25uy.js +0 -1
  84. chainlit/frontend/dist/assets/Twitch-y9iKCcM1.js +0 -1
  85. chainlit/frontend/dist/assets/Vidyard-ClYvcuEu.js +0 -1
  86. chainlit/frontend/dist/assets/Vimeo-D6HvM2jt.js +0 -1
  87. chainlit/frontend/dist/assets/Wistia-Cu4zZ2Ci.js +0 -1
  88. chainlit/frontend/dist/assets/YouTube-D10tR6CJ.js +0 -1
  89. chainlit/frontend/dist/assets/index-CI4qFOt5.js +0 -8665
  90. chainlit/frontend/dist/assets/index-CrrqM0nZ.css +0 -1
  91. chainlit/translations/nl-NL.json +0 -229
  92. chainlit-2.0.0.dist-info/RECORD +0 -106
  93. /chainlit/copilot/dist/assets/{logo_dark-IkGJ_IwC.svg → logo_dark-2a3cf740.svg} +0 -0
  94. /chainlit/copilot/dist/assets/{logo_light-Bb_IPh6r.svg → logo_light-b078e7bc.svg} +0 -0
  95. /chainlit/frontend/dist/assets/{logo_dark-IkGJ_IwC.svg → logo_dark-2a3cf740.svg} +0 -0
  96. /chainlit/frontend/dist/assets/{logo_light-Bb_IPh6r.svg → logo_light-b078e7bc.svg} +0 -0
  97. {chainlit-2.0.0.dist-info → chainlit-2.0.dev0.dist-info}/WHEEL +0 -0
  98. {chainlit-2.0.0.dist-info → chainlit-2.0.dev0.dist-info}/entry_points.txt +0 -0
chainlit/session.py CHANGED
@@ -6,7 +6,6 @@ import uuid
6
6
  from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, Literal, Optional, Union
7
7
 
8
8
  import aiofiles
9
-
10
9
  from chainlit.logger import logger
11
10
  from chainlit.types import FileReference
12
11
 
@@ -18,9 +17,9 @@ ClientType = Literal["webapp", "copilot", "teams", "slack", "discord"]
18
17
 
19
18
 
20
19
  class JSONEncoderIgnoreNonSerializable(json.JSONEncoder):
21
- def default(self, o):
20
+ def default(self, obj):
22
21
  try:
23
- return super().default(o)
22
+ return super(JSONEncoderIgnoreNonSerializable, self).default(obj)
24
23
  except TypeError:
25
24
  return None
26
25
 
@@ -113,10 +112,9 @@ class BaseSession:
113
112
 
114
113
  if path:
115
114
  # Copy the file from the given path
116
- async with (
117
- aiofiles.open(path, "rb") as src,
118
- aiofiles.open(file_path, "wb") as dst,
119
- ):
115
+ async with aiofiles.open(path, "rb") as src, aiofiles.open(
116
+ file_path, "wb"
117
+ ) as dst:
120
118
  await dst.write(await src.read())
121
119
  elif content:
122
120
  # Write the provided content to the file
@@ -1,6 +1,6 @@
1
- import importlib.util
2
-
3
- if importlib.util.find_spec("slack_bolt") is None:
1
+ try:
2
+ import slack_bolt
3
+ except ModuleNotFoundError:
4
4
  raise ValueError(
5
5
  "The slack_bolt package is required to integrate Chainlit with a Slack app. Run `pip install slack_bolt --upgrade`"
6
6
  )
chainlit/slack/app.py CHANGED
@@ -7,9 +7,6 @@ from functools import partial
7
7
  from typing import Dict, List, Optional, Union
8
8
 
9
9
  import httpx
10
- from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
11
- from slack_bolt.async_app import AsyncApp
12
-
13
10
  from chainlit.config import config
14
11
  from chainlit.context import ChainlitContext, HTTPSession, context, context_var
15
12
  from chainlit.data import get_data_layer
@@ -21,6 +18,8 @@ from chainlit.telemetry import trace
21
18
  from chainlit.types import Feedback
22
19
  from chainlit.user import PersistedUser, User
23
20
  from chainlit.user_session import user_session
21
+ from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
22
+ from slack_bolt.async_app import AsyncApp
24
23
 
25
24
 
26
25
  class SlackEmitter(BaseChainlitEmitter):
chainlit/socket.py CHANGED
@@ -1,28 +1,29 @@
1
1
  import asyncio
2
2
  import json
3
3
  import time
4
- from typing import Any, Dict, Literal, Optional, Tuple, Union
4
+ import uuid
5
+ from typing import Any, Dict, Literal
5
6
  from urllib.parse import unquote
6
7
 
7
- from starlette.requests import cookie_parser
8
- from typing_extensions import TypeAlias
9
-
8
+ from chainlit.action import Action
10
9
  from chainlit.auth import get_current_user, require_login
11
10
  from chainlit.chat_context import chat_context
12
11
  from chainlit.config import config
13
12
  from chainlit.context import init_ws_context
14
13
  from chainlit.data import get_data_layer
14
+ from chainlit.element import Element
15
15
  from chainlit.logger import logger
16
16
  from chainlit.message import ErrorMessage, Message
17
17
  from chainlit.server import sio
18
18
  from chainlit.session import WebsocketSession
19
19
  from chainlit.telemetry import trace_event
20
- from chainlit.types import InputAudioChunk, InputAudioChunkPayload, MessagePayload
21
- from chainlit.user import PersistedUser, User
20
+ from chainlit.types import (
21
+ InputAudioChunk,
22
+ InputAudioChunkPayload,
23
+ MessagePayload,
24
+ )
22
25
  from chainlit.user_session import user_sessions
23
26
 
24
- WSGIEnvironment: TypeAlias = dict[str, Any]
25
-
26
27
 
27
28
  def restore_existing_session(sid, session_id, emit_fn, emit_call_fn):
28
29
  """Restore a session from the sessionId provided by the client."""
@@ -81,44 +82,45 @@ def load_user_env(user_env):
81
82
  return user_env
82
83
 
83
84
 
84
- def _get_token_from_cookie(environ: WSGIEnvironment) -> Optional[str]:
85
- if cookie_header := environ.get("HTTP_COOKIE", None):
86
- cookies = cookie_parser(cookie_header)
87
- return cookies.get("access_token", None)
88
-
89
- return None
90
-
91
-
92
- def _get_token(environ: WSGIEnvironment, auth: dict) -> Optional[str]:
93
- """Take WSGI environ, return access token."""
94
- return _get_token_from_cookie(environ)
95
-
96
-
97
- async def _authenticate_connection(
98
- environ,
99
- auth,
100
- ) -> Union[Tuple[Union[User, PersistedUser], str], Tuple[None, None]]:
101
- if token := _get_token(environ, auth):
102
- user = await get_current_user(token=token)
103
- if user:
104
- return user, token
105
-
106
- return None, None
107
-
108
-
109
- @sio.on("connect") # pyright: ignore [reportOptionalCall]
110
- async def connect(sid, environ, auth):
111
- user = token = None
112
-
113
- if require_login():
114
- try:
115
- user, token = await _authenticate_connection(environ, auth)
116
- except Exception as e:
117
- logger.exception("Exception authenticating connection: %s", e)
85
+ def build_anon_user_identifier(environ):
86
+ scope = environ.get("asgi.scope", {})
87
+ client_ip, _ = scope.get("client")
88
+ ip = environ.get("HTTP_X_FORWARDED_FOR", client_ip)
118
89
 
119
- if not user:
120
- logger.error("Authentication failed in websocket connect.")
121
- raise ConnectionRefusedError("authentication failed")
90
+ try:
91
+ headers = scope.get("headers", {})
92
+ user_agent = next(
93
+ (v.decode("utf-8") for k, v in headers if k.decode("utf-8") == "user-agent")
94
+ )
95
+ return str(uuid.uuid5(uuid.NAMESPACE_DNS, user_agent + ip))
96
+
97
+ except StopIteration:
98
+ return str(uuid.uuid5(uuid.NAMESPACE_DNS, ip))
99
+
100
+
101
+ @sio.on("connect")
102
+ async def connect(sid, environ):
103
+ if (
104
+ not config.code.on_chat_start
105
+ and not config.code.on_message
106
+ and not config.code.on_audio_chunk
107
+ ):
108
+ logger.warning(
109
+ "You need to configure at least one of on_chat_start, on_message or on_audio_chunk callback"
110
+ )
111
+ return False
112
+ user = None
113
+ token = None
114
+ login_required = require_login()
115
+ try:
116
+ # Check if the authentication is required
117
+ if login_required:
118
+ authorization_header = environ.get("HTTP_AUTHORIZATION")
119
+ token = authorization_header.split(" ")[1] if authorization_header else None
120
+ user = await get_current_user(token=token)
121
+ except Exception:
122
+ logger.info("Authentication failed")
123
+ return False
122
124
 
123
125
  # Session scoped function to emit to the client
124
126
  def emit_fn(event, data):
@@ -128,16 +130,16 @@ async def connect(sid, environ, auth):
128
130
  def emit_call_fn(event: Literal["ask", "call_fn"], data, timeout):
129
131
  return sio.call(event, data, timeout=timeout, to=sid)
130
132
 
131
- session_id = auth.get("sessionId")
133
+ session_id = environ.get("HTTP_X_CHAINLIT_SESSION_ID")
132
134
  if restore_existing_session(sid, session_id, emit_fn, emit_call_fn):
133
135
  return True
134
136
 
135
- user_env_string = auth.get("userEnv")
137
+ user_env_string = environ.get("HTTP_USER_ENV")
136
138
  user_env = load_user_env(user_env_string)
137
139
 
138
- client_type = auth.get("clientType")
140
+ client_type = environ.get("HTTP_X_CHAINLIT_CLIENT_TYPE")
139
141
  http_referer = environ.get("HTTP_REFERER")
140
- url_encoded_chat_profile = auth.get("chatProfile")
142
+ url_encoded_chat_profile = environ.get("HTTP_X_CHAINLIT_CHAT_PROFILE")
141
143
  chat_profile = (
142
144
  unquote(url_encoded_chat_profile) if url_encoded_chat_profile else None
143
145
  )
@@ -152,7 +154,7 @@ async def connect(sid, environ, auth):
152
154
  user=user,
153
155
  token=token,
154
156
  chat_profile=chat_profile,
155
- thread_id=auth.get("threadId"),
157
+ thread_id=environ.get("HTTP_X_CHAINLIT_THREAD_ID"),
156
158
  languages=environ.get("HTTP_ACCEPT_LANGUAGE"),
157
159
  http_referer=http_referer,
158
160
  )
@@ -161,17 +163,17 @@ async def connect(sid, environ, auth):
161
163
  return True
162
164
 
163
165
 
164
- @sio.on("connection_successful") # pyright: ignore [reportOptionalCall]
166
+ @sio.on("connection_successful")
165
167
  async def connection_successful(sid):
166
168
  context = init_ws_context(sid)
167
169
 
170
+ if context.session.restored:
171
+ return
172
+
168
173
  await context.emitter.task_end()
169
174
  await context.emitter.clear("clear_ask")
170
175
  await context.emitter.clear("clear_call_fn")
171
176
 
172
- if context.session.restored:
173
- return
174
-
175
177
  if context.session.thread_id_to_resume and config.code.on_chat_resume:
176
178
  thread = await resume_thread(context.session)
177
179
  if thread:
@@ -194,14 +196,14 @@ async def connection_successful(sid):
194
196
  context.session.current_task = task
195
197
 
196
198
 
197
- @sio.on("clear_session") # pyright: ignore [reportOptionalCall]
199
+ @sio.on("clear_session")
198
200
  async def clean_session(sid):
199
201
  session = WebsocketSession.get(sid)
200
202
  if session:
201
203
  session.to_clear = True
202
204
 
203
205
 
204
- @sio.on("disconnect") # pyright: ignore [reportOptionalCall]
206
+ @sio.on("disconnect")
205
207
  async def disconnect(sid):
206
208
  session = WebsocketSession.get(sid)
207
209
 
@@ -235,7 +237,7 @@ async def disconnect(sid):
235
237
  asyncio.ensure_future(clear_on_timeout(sid))
236
238
 
237
239
 
238
- @sio.on("stop") # pyright: ignore [reportOptionalCall]
240
+ @sio.on("stop")
239
241
  async def stop(sid):
240
242
  if session := WebsocketSession.get(sid):
241
243
  trace_event("stop_task")
@@ -272,7 +274,7 @@ async def process_message(session: WebsocketSession, payload: MessagePayload):
272
274
  await context.emitter.task_end()
273
275
 
274
276
 
275
- @sio.on("edit_message") # pyright: ignore [reportOptionalCall]
277
+ @sio.on("edit_message")
276
278
  async def edit_message(sid, payload: MessagePayload):
277
279
  """Handle a message sent by the User."""
278
280
  session = WebsocketSession.require(sid)
@@ -302,7 +304,7 @@ async def edit_message(sid, payload: MessagePayload):
302
304
  await context.emitter.task_end()
303
305
 
304
306
 
305
- @sio.on("client_message") # pyright: ignore [reportOptionalCall]
307
+ @sio.on("client_message")
306
308
  async def message(sid, payload: MessagePayload):
307
309
  """Handle a message sent by the User."""
308
310
  session = WebsocketSession.require(sid)
@@ -311,30 +313,17 @@ async def message(sid, payload: MessagePayload):
311
313
  session.current_task = task
312
314
 
313
315
 
314
- @sio.on("window_message") # pyright: ignore [reportOptionalCall]
315
- async def window_message(sid, data):
316
- """Handle a message send by the host window."""
317
- session = WebsocketSession.require(sid)
318
- init_ws_context(session)
319
-
320
- if config.code.on_window_message:
321
- try:
322
- await config.code.on_window_message(data)
323
- except asyncio.CancelledError:
324
- pass
325
-
326
-
327
- @sio.on("audio_start") # pyright: ignore [reportOptionalCall]
316
+ @sio.on("audio_start")
328
317
  async def audio_start(sid):
329
318
  """Handle audio init."""
330
319
  session = WebsocketSession.require(sid)
331
320
 
332
321
  context = init_ws_context(session)
333
322
  if config.code.on_audio_start:
334
- connected = bool(await config.code.on_audio_start())
335
- connection_state = "on" if connected else "off"
336
- await context.emitter.update_audio_connection(connection_state)
337
-
323
+ connected = bool(await config.code.on_audio_start())
324
+ connection_state = "on" if connected else "off"
325
+ await context.emitter.update_audio_connection(connection_state)
326
+
338
327
 
339
328
  @sio.on("audio_chunk")
340
329
  async def audio_chunk(sid, payload: InputAudioChunkPayload):
@@ -361,7 +350,7 @@ async def audio_end(sid):
361
350
 
362
351
  if config.code.on_audio_end:
363
352
  await config.code.on_audio_end()
364
-
353
+
365
354
  except asyncio.CancelledError:
366
355
  pass
367
356
  except Exception as e:
@@ -373,6 +362,42 @@ async def audio_end(sid):
373
362
  await context.emitter.task_end()
374
363
 
375
364
 
365
+ async def process_action(action: Action):
366
+ callback = config.code.action_callbacks.get(action.name)
367
+ if callback:
368
+ res = await callback(action)
369
+ return res
370
+ else:
371
+ logger.warning("No callback found for action %s", action.name)
372
+
373
+
374
+ @sio.on("action_call")
375
+ async def call_action(sid, action):
376
+ """Handle an action call from the UI."""
377
+ context = init_ws_context(sid)
378
+
379
+ action = Action(**action)
380
+
381
+ try:
382
+ if not context.session.has_first_interaction:
383
+ context.session.has_first_interaction = True
384
+ asyncio.create_task(context.emitter.init_thread(action.name))
385
+ res = await process_action(action)
386
+ await context.emitter.send_action_response(
387
+ id=action.id, status=True, response=res if isinstance(res, str) else None
388
+ )
389
+
390
+ except asyncio.CancelledError:
391
+ await context.emitter.send_action_response(
392
+ id=action.id, status=False, response="Action interrupted by the user"
393
+ )
394
+ except Exception as e:
395
+ logger.exception(e)
396
+ await context.emitter.send_action_response(
397
+ id=action.id, status=False, response="An error occurred"
398
+ )
399
+
400
+
376
401
  @sio.on("chat_settings_change")
377
402
  async def change_settings(sid, settings: Dict[str, Any]):
378
403
  """Handle change settings submit from the UI."""
chainlit/step.py CHANGED
@@ -7,10 +7,6 @@ from copy import deepcopy
7
7
  from functools import wraps
8
8
  from typing import Callable, Dict, List, Optional, TypedDict, Union
9
9
 
10
- from literalai import BaseGeneration
11
- from literalai.helper import utc_now
12
- from literalai.observability.step import StepType, TrueStepType
13
-
14
10
  from chainlit.config import config
15
11
  from chainlit.context import CL_RUN_NAMES, context, local_steps
16
12
  from chainlit.data import get_data_layer
@@ -18,6 +14,9 @@ from chainlit.element import Element
18
14
  from chainlit.logger import logger
19
15
  from chainlit.telemetry import trace_event
20
16
  from chainlit.types import FeedbackDict
17
+ from literalai import BaseGeneration
18
+ from literalai.helper import utc_now
19
+ from literalai.observability.step import StepType, TrueStepType
21
20
 
22
21
 
23
22
  def check_add_step_in_cot(step: "Step"):
@@ -62,10 +61,11 @@ class StepDict(TypedDict, total=False):
62
61
  generation: Optional[Dict]
63
62
  showInput: Optional[Union[bool, str]]
64
63
  language: Optional[str]
64
+ indent: Optional[int]
65
65
  feedback: Optional[FeedbackDict]
66
66
 
67
67
 
68
- def flatten_args_kwargs(func, args, kwargs):
68
+ def flatten_args_kwargs(func, *args, **kwargs):
69
69
  signature = inspect.signature(func)
70
70
  bound_arguments = signature.bind(*args, **kwargs)
71
71
  bound_arguments.apply_defaults()
@@ -107,8 +107,8 @@ def step(
107
107
  ) as step:
108
108
  try:
109
109
  step.input = flatten_args_kwargs(func, args, kwargs)
110
- except Exception as e:
111
- logger.exception(e)
110
+ except:
111
+ pass
112
112
  result = await func(*args, **kwargs)
113
113
  try:
114
114
  if result and not step.output:
@@ -134,13 +134,13 @@ def step(
134
134
  ) as step:
135
135
  try:
136
136
  step.input = flatten_args_kwargs(func, args, kwargs)
137
- except Exception as e:
138
- logger.exception(e)
137
+ except:
138
+ pass
139
139
  result = func(*args, **kwargs)
140
140
  try:
141
141
  if result and not step.output:
142
142
  step.output = result
143
- except Exception as e:
143
+ except:
144
144
  step.is_error = True
145
145
  step.output = str(e)
146
146
  return result
@@ -317,7 +317,7 @@ class Step:
317
317
  except Exception as e:
318
318
  if self.fail_on_persist_error:
319
319
  raise e
320
- logger.error(f"Failed to persist step update: {e!s}")
320
+ logger.error(f"Failed to persist step update: {str(e)}")
321
321
 
322
322
  tasks = [el.send(for_id=self.id) for el in self.elements]
323
323
  await asyncio.gather(*tasks)
@@ -344,7 +344,7 @@ class Step:
344
344
  except Exception as e:
345
345
  if self.fail_on_persist_error:
346
346
  raise e
347
- logger.error(f"Failed to persist step deletion: {e!s}")
347
+ logger.error(f"Failed to persist step deletion: {str(e)}")
348
348
 
349
349
  await context.emitter.delete_step(step_dict)
350
350
 
@@ -371,7 +371,7 @@ class Step:
371
371
  except Exception as e:
372
372
  if self.fail_on_persist_error:
373
373
  raise e
374
- logger.error(f"Failed to persist step creation: {e!s}")
374
+ logger.error(f"Failed to persist step creation: {str(e)}")
375
375
 
376
376
  tasks = [el.send(for_id=self.id) for el in self.elements]
377
377
  await asyncio.gather(*tasks)
@@ -434,6 +434,7 @@ class Step:
434
434
  if not self.parent_id:
435
435
  if parent_step:
436
436
  self.parent_id = parent_step.id
437
+ context.active_steps.append(self)
437
438
  local_steps.set(previous_steps + [self])
438
439
  await self.send()
439
440
  return self
@@ -445,10 +446,13 @@ class Step:
445
446
  self.output = str(exc_val)
446
447
  self.is_error = True
447
448
 
448
- current_steps = local_steps.get()
449
- if current_steps and self in current_steps:
450
- current_steps.remove(self)
451
- local_steps.set(current_steps)
449
+ if self in context.active_steps:
450
+ context.active_steps.remove(self)
451
+
452
+ local_active_steps = local_steps.get()
453
+ if local_active_steps and self in local_active_steps:
454
+ local_active_steps.remove(self)
455
+ local_steps.set(local_active_steps)
452
456
 
453
457
  await self.update()
454
458
 
@@ -461,6 +465,7 @@ class Step:
461
465
  if not self.parent_id:
462
466
  if parent_step:
463
467
  self.parent_id = parent_step.id
468
+ context.active_steps.append(self)
464
469
  local_steps.set(previous_steps + [self])
465
470
 
466
471
  asyncio.create_task(self.send())
@@ -473,9 +478,12 @@ class Step:
473
478
  self.output = str(exc_val)
474
479
  self.is_error = True
475
480
 
476
- current_steps = local_steps.get()
477
- if current_steps and self in current_steps:
478
- current_steps.remove(self)
479
- local_steps.set(current_steps)
481
+ if self in context.active_steps:
482
+ context.active_steps.remove(self)
483
+
484
+ local_active_steps = local_steps.get()
485
+ if local_active_steps and self in local_active_steps:
486
+ local_active_steps.remove(self)
487
+ local_steps.set(local_active_steps)
480
488
 
481
489
  asyncio.create_task(self.update())
chainlit/sync.py CHANGED
@@ -10,9 +10,8 @@ import asyncio
10
10
  import threading
11
11
 
12
12
  from asyncer import asyncify
13
- from syncer import sync
14
-
15
13
  from chainlit.context import context_var
14
+ from syncer import sync
16
15
 
17
16
  make_async = asyncify
18
17
 
@@ -1,6 +1,6 @@
1
- import importlib.util
2
-
3
- if importlib.util.find_spec("botbuilder") is None:
1
+ try:
2
+ import botbuilder
3
+ except ModuleNotFoundError:
4
4
  raise ValueError(
5
5
  "The botbuilder-core package is required to integrate Chainlit with a Slack app. Run `pip install botbuilder-core --upgrade`"
6
6
  )
chainlit/teams/app.py CHANGED
@@ -28,7 +28,6 @@ from botbuilder.schema import (
28
28
  ChannelAccount,
29
29
  HeroCard,
30
30
  )
31
-
32
31
  from chainlit.config import config
33
32
  from chainlit.context import ChainlitContext, HTTPSession, context, context_var
34
33
  from chainlit.data import get_data_layer
chainlit/types.py CHANGED
@@ -79,7 +79,7 @@ T = TypeVar("T", covariant=True)
79
79
  class HasFromDict(Protocol[T]):
80
80
  @classmethod
81
81
  def from_dict(cls, obj_dict: Any) -> T:
82
- raise NotImplementedError
82
+ raise NotImplementedError()
83
83
 
84
84
 
85
85
  @dataclass
@@ -168,13 +168,11 @@ class InputAudioChunk:
168
168
  elapsedTime: float
169
169
  data: bytes
170
170
 
171
-
172
171
  class OutputAudioChunk(TypedDict):
173
172
  track: str
174
173
  mimeType: str
175
174
  data: bytes
176
175
 
177
-
178
176
  @dataclass
179
177
  class AskFileResponse:
180
178
  id: str
@@ -186,16 +184,12 @@ class AskFileResponse:
186
184
 
187
185
  class AskActionResponse(TypedDict):
188
186
  name: str
189
- payload: Dict
187
+ value: str
190
188
  label: str
191
- tooltip: str
189
+ description: str
192
190
  forId: str
193
191
  id: str
194
-
195
-
196
- class UpdateThreadRequest(BaseModel):
197
- threadId: str
198
- name: str
192
+ collapsed: bool
199
193
 
200
194
 
201
195
  class DeleteThreadRequest(BaseModel):
@@ -211,16 +205,6 @@ class GetThreadsRequest(BaseModel):
211
205
  filter: ThreadFilter
212
206
 
213
207
 
214
- class CallActionRequest(BaseModel):
215
- action: Dict
216
- sessionId: str
217
-
218
-
219
- class ElementRequest(BaseModel):
220
- element: Dict
221
- sessionId: str
222
-
223
-
224
208
  class Theme(str, Enum):
225
209
  light = "light"
226
210
  dark = "dark"
chainlit/user.py CHANGED
@@ -1,8 +1,7 @@
1
1
  from typing import Dict, Literal, Optional, TypedDict
2
2
 
3
3
  from dataclasses_json import DataClassJsonMixin
4
- from pydantic import Field
5
- from pydantic.dataclasses import dataclass
4
+ from pydantic.dataclasses import Field, dataclass
6
5
 
7
6
  Provider = Literal[
8
7
  "credentials",
chainlit/utils.py CHANGED
@@ -6,13 +6,12 @@ from asyncio import CancelledError
6
6
  from typing import Callable
7
7
 
8
8
  import click
9
- from fastapi import FastAPI
10
- from packaging import version
11
-
12
9
  from chainlit.auth import ensure_jwt_secret
13
10
  from chainlit.context import context
14
11
  from chainlit.logger import logger
15
12
  from chainlit.message import ErrorMessage
13
+ from fastapi import FastAPI
14
+ from packaging import version
16
15
 
17
16
 
18
17
  def wrap_user_function(user_function: Callable, with_task=False) -> Callable:
chainlit/version.py CHANGED
@@ -3,6 +3,5 @@ from importlib import metadata
3
3
  try:
4
4
  __version__ = metadata.version(__package__)
5
5
  except metadata.PackageNotFoundError:
6
- # Case where package metadata is not available, default to a 'non-outdated' version.
7
- # Ref: config.py::load_settings()
8
- __version__ = "0.3.1"
6
+ # Case where package metadata is not available.
7
+ __version__ = ""