chainlit 1.0.401__py3-none-any.whl → 2.0.3__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 (112) hide show
  1. chainlit/__init__.py +98 -279
  2. chainlit/_utils.py +8 -0
  3. chainlit/action.py +12 -10
  4. chainlit/{auth.py → auth/__init__.py} +28 -36
  5. chainlit/auth/cookie.py +122 -0
  6. chainlit/auth/jwt.py +39 -0
  7. chainlit/cache.py +4 -6
  8. chainlit/callbacks.py +362 -0
  9. chainlit/chat_context.py +64 -0
  10. chainlit/chat_settings.py +3 -1
  11. chainlit/cli/__init__.py +77 -8
  12. chainlit/config.py +181 -101
  13. chainlit/context.py +42 -13
  14. chainlit/copilot/dist/index.js +8750 -903
  15. chainlit/data/__init__.py +101 -416
  16. chainlit/data/acl.py +6 -2
  17. chainlit/data/base.py +107 -0
  18. chainlit/data/chainlit_data_layer.py +608 -0
  19. chainlit/data/dynamodb.py +590 -0
  20. chainlit/data/literalai.py +500 -0
  21. chainlit/data/sql_alchemy.py +721 -0
  22. chainlit/data/storage_clients/__init__.py +0 -0
  23. chainlit/data/storage_clients/azure.py +81 -0
  24. chainlit/data/storage_clients/azure_blob.py +89 -0
  25. chainlit/data/storage_clients/base.py +26 -0
  26. chainlit/data/storage_clients/gcs.py +88 -0
  27. chainlit/data/storage_clients/s3.py +75 -0
  28. chainlit/data/utils.py +29 -0
  29. chainlit/discord/__init__.py +6 -0
  30. chainlit/discord/app.py +354 -0
  31. chainlit/element.py +91 -33
  32. chainlit/emitter.py +80 -29
  33. chainlit/frontend/dist/assets/DailyMotion-C_XC7xJI.js +1 -0
  34. chainlit/frontend/dist/assets/Dataframe-Cs4l4hA1.js +22 -0
  35. chainlit/frontend/dist/assets/Facebook-CUeCH7hk.js +1 -0
  36. chainlit/frontend/dist/assets/FilePlayer-CB-fYkx8.js +1 -0
  37. chainlit/frontend/dist/assets/Kaltura-YX6qaq72.js +1 -0
  38. chainlit/frontend/dist/assets/Mixcloud-DGV0ldjP.js +1 -0
  39. chainlit/frontend/dist/assets/Mux-CmRss5oc.js +1 -0
  40. chainlit/frontend/dist/assets/Preview-DBVJn7-H.js +1 -0
  41. chainlit/frontend/dist/assets/SoundCloud-qLUb18oY.js +1 -0
  42. chainlit/frontend/dist/assets/Streamable-BvYP7bFp.js +1 -0
  43. chainlit/frontend/dist/assets/Twitch-CTHt-sGZ.js +1 -0
  44. chainlit/frontend/dist/assets/Vidyard-B-0mCJbm.js +1 -0
  45. chainlit/frontend/dist/assets/Vimeo-Dnp7ri8q.js +1 -0
  46. chainlit/frontend/dist/assets/Wistia-DW0x_UBn.js +1 -0
  47. chainlit/frontend/dist/assets/YouTube--98FipvA.js +1 -0
  48. chainlit/frontend/dist/assets/index-D71nZ46o.js +8665 -0
  49. chainlit/frontend/dist/assets/index-g8LTJwwr.css +1 -0
  50. chainlit/frontend/dist/assets/react-plotly-Cn_BQTQw.js +3484 -0
  51. chainlit/frontend/dist/index.html +2 -4
  52. chainlit/haystack/callbacks.py +4 -7
  53. chainlit/input_widget.py +8 -4
  54. chainlit/langchain/callbacks.py +103 -68
  55. chainlit/langflow/__init__.py +1 -0
  56. chainlit/llama_index/callbacks.py +65 -40
  57. chainlit/markdown.py +22 -6
  58. chainlit/message.py +54 -56
  59. chainlit/mistralai/__init__.py +50 -0
  60. chainlit/oauth_providers.py +266 -8
  61. chainlit/openai/__init__.py +10 -18
  62. chainlit/secret.py +1 -1
  63. chainlit/server.py +789 -228
  64. chainlit/session.py +108 -90
  65. chainlit/slack/__init__.py +6 -0
  66. chainlit/slack/app.py +397 -0
  67. chainlit/socket.py +199 -116
  68. chainlit/step.py +141 -89
  69. chainlit/sync.py +2 -1
  70. chainlit/teams/__init__.py +6 -0
  71. chainlit/teams/app.py +338 -0
  72. chainlit/translations/bn.json +235 -0
  73. chainlit/translations/en-US.json +83 -4
  74. chainlit/translations/gu.json +235 -0
  75. chainlit/translations/he-IL.json +235 -0
  76. chainlit/translations/hi.json +235 -0
  77. chainlit/translations/kn.json +235 -0
  78. chainlit/translations/ml.json +235 -0
  79. chainlit/translations/mr.json +235 -0
  80. chainlit/translations/nl-NL.json +233 -0
  81. chainlit/translations/ta.json +235 -0
  82. chainlit/translations/te.json +235 -0
  83. chainlit/translations/zh-CN.json +233 -0
  84. chainlit/translations.py +60 -0
  85. chainlit/types.py +133 -28
  86. chainlit/user.py +14 -3
  87. chainlit/user_session.py +6 -3
  88. chainlit/utils.py +52 -5
  89. chainlit/version.py +3 -2
  90. {chainlit-1.0.401.dist-info → chainlit-2.0.3.dist-info}/METADATA +48 -50
  91. chainlit-2.0.3.dist-info/RECORD +106 -0
  92. chainlit/cli/utils.py +0 -24
  93. chainlit/frontend/dist/assets/index-9711593e.js +0 -723
  94. chainlit/frontend/dist/assets/index-d088547c.css +0 -1
  95. chainlit/frontend/dist/assets/react-plotly-d8762cc2.js +0 -3602
  96. chainlit/playground/__init__.py +0 -2
  97. chainlit/playground/config.py +0 -40
  98. chainlit/playground/provider.py +0 -108
  99. chainlit/playground/providers/__init__.py +0 -13
  100. chainlit/playground/providers/anthropic.py +0 -118
  101. chainlit/playground/providers/huggingface.py +0 -75
  102. chainlit/playground/providers/langchain.py +0 -89
  103. chainlit/playground/providers/openai.py +0 -408
  104. chainlit/playground/providers/vertexai.py +0 -171
  105. chainlit/translations/pt-BR.json +0 -155
  106. chainlit-1.0.401.dist-info/RECORD +0 -66
  107. /chainlit/copilot/dist/assets/{logo_dark-2a3cf740.svg → logo_dark-IkGJ_IwC.svg} +0 -0
  108. /chainlit/copilot/dist/assets/{logo_light-b078e7bc.svg → logo_light-Bb_IPh6r.svg} +0 -0
  109. /chainlit/frontend/dist/assets/{logo_dark-2a3cf740.svg → logo_dark-IkGJ_IwC.svg} +0 -0
  110. /chainlit/frontend/dist/assets/{logo_light-b078e7bc.svg → logo_light-Bb_IPh6r.svg} +0 -0
  111. {chainlit-1.0.401.dist-info → chainlit-2.0.3.dist-info}/WHEEL +0 -0
  112. {chainlit-1.0.401.dist-info → chainlit-2.0.3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,122 @@
1
+ import os
2
+ from typing import Literal, Optional, cast
3
+
4
+ from fastapi import Request, Response
5
+ from fastapi.exceptions import HTTPException
6
+ from fastapi.security.base import SecurityBase
7
+ from fastapi.security.utils import get_authorization_scheme_param
8
+ from starlette.status import HTTP_401_UNAUTHORIZED
9
+
10
+ """ Module level cookie settings. """
11
+ _cookie_samesite = cast(
12
+ Literal["lax", "strict", "none"],
13
+ os.environ.get("CHAINLIT_COOKIE_SAMESITE", "lax"),
14
+ )
15
+
16
+ assert _cookie_samesite in [
17
+ "lax",
18
+ "strict",
19
+ "none",
20
+ ], (
21
+ "Invalid value for CHAINLIT_COOKIE_SAMESITE. Must be one of 'lax', 'strict' or 'none'."
22
+ )
23
+ _cookie_secure = _cookie_samesite == "none"
24
+
25
+ _auth_cookie_lifetime = 60 * 60 # 1 hour
26
+ _state_cookie_lifetime = 3 * 60 # 3m
27
+ _auth_cookie_name = "access_token"
28
+ _state_cookie_name = "oauth_state"
29
+
30
+
31
+ class OAuth2PasswordBearerWithCookie(SecurityBase):
32
+ """
33
+ OAuth2 password flow with cookie support with fallback to bearer token.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ tokenUrl: str,
39
+ scheme_name: Optional[str] = None,
40
+ auto_error: bool = True,
41
+ ):
42
+ self.tokenUrl = tokenUrl
43
+ self.scheme_name = scheme_name or self.__class__.__name__
44
+ self.auto_error = auto_error
45
+
46
+ async def __call__(self, request: Request) -> Optional[str]:
47
+ # First try to get the token from the cookie
48
+ token = request.cookies.get(_auth_cookie_name)
49
+
50
+ # If no cookie, try the Authorization header as fallback
51
+ if not token:
52
+ # TODO: Only bother to check if cookie auth is explicitly disabled.
53
+ authorization = request.headers.get("Authorization")
54
+ if authorization:
55
+ scheme, token = get_authorization_scheme_param(authorization)
56
+ if scheme.lower() != "bearer":
57
+ if self.auto_error:
58
+ raise HTTPException(
59
+ status_code=HTTP_401_UNAUTHORIZED,
60
+ detail="Invalid authentication credentials",
61
+ headers={"WWW-Authenticate": "Bearer"},
62
+ )
63
+ else:
64
+ return None
65
+ else:
66
+ if self.auto_error:
67
+ raise HTTPException(
68
+ status_code=HTTP_401_UNAUTHORIZED,
69
+ detail="Not authenticated",
70
+ headers={"WWW-Authenticate": "Bearer"},
71
+ )
72
+ else:
73
+ return None
74
+
75
+ return token
76
+
77
+
78
+ def set_auth_cookie(response: Response, token: str):
79
+ """
80
+ Helper function to set the authentication cookie with secure parameters
81
+ """
82
+
83
+ response.set_cookie(
84
+ key=_auth_cookie_name,
85
+ value=token,
86
+ httponly=True,
87
+ secure=_cookie_secure,
88
+ samesite=_cookie_samesite,
89
+ max_age=_auth_cookie_lifetime,
90
+ )
91
+
92
+
93
+ def clear_auth_cookie(response: Response):
94
+ """
95
+ Helper function to clear the authentication cookie
96
+ """
97
+ response.delete_cookie(key=_auth_cookie_name, path="/")
98
+
99
+
100
+ def set_oauth_state_cookie(response: Response, token: str):
101
+ response.set_cookie(
102
+ _state_cookie_name,
103
+ token,
104
+ httponly=True,
105
+ samesite=_cookie_samesite,
106
+ secure=_cookie_secure,
107
+ max_age=_state_cookie_lifetime,
108
+ )
109
+
110
+
111
+ def validate_oauth_state_cookie(request: Request, state: str):
112
+ """Check the state from the oauth provider against the browser cookie."""
113
+
114
+ oauth_state = request.cookies.get(_state_cookie_name)
115
+
116
+ if oauth_state != state:
117
+ raise Exception("oauth state does not correspond")
118
+
119
+
120
+ def clear_oauth_state_cookie(response: Response):
121
+ """Oauth complete, delete state token."""
122
+ response.delete_cookie(_state_cookie_name) # Do we set path here?
chainlit/auth/jwt.py ADDED
@@ -0,0 +1,39 @@
1
+ import os
2
+ from datetime import datetime, timedelta, timezone
3
+ from typing import Any, Dict, Optional
4
+
5
+ import jwt as pyjwt
6
+
7
+ from chainlit.config import config
8
+ from chainlit.user import User
9
+
10
+
11
+ def get_jwt_secret() -> Optional[str]:
12
+ return os.environ.get("CHAINLIT_AUTH_SECRET")
13
+
14
+
15
+ def create_jwt(data: User) -> str:
16
+ to_encode: Dict[str, Any] = data.to_dict()
17
+ to_encode.update(
18
+ {
19
+ "exp": datetime.now(timezone.utc)
20
+ + timedelta(seconds=config.project.user_session_timeout),
21
+ "iat": datetime.now(timezone.utc), # Add issued at time
22
+ }
23
+ )
24
+
25
+ secret = get_jwt_secret()
26
+ assert secret
27
+ encoded_jwt = pyjwt.encode(to_encode, secret, algorithm="HS256")
28
+ return encoded_jwt
29
+
30
+
31
+ def decode_jwt(token: str) -> User:
32
+ dict = pyjwt.decode(
33
+ token,
34
+ get_jwt_secret(),
35
+ algorithms=["HS256"],
36
+ options={"verify_signature": True},
37
+ )
38
+ del dict["exp"]
39
+ return User(**dict)
chainlit/cache.py CHANGED
@@ -1,5 +1,7 @@
1
+ import importlib.util
1
2
  import os
2
3
  import threading
4
+ from typing import Any
3
5
 
4
6
  from chainlit.config import config
5
7
  from chainlit.logger import logger
@@ -8,11 +10,7 @@ from chainlit.logger import logger
8
10
  def init_lc_cache():
9
11
  use_cache = config.project.cache is True and config.run.no_cache is False
10
12
 
11
- if use_cache:
12
- try:
13
- import langchain
14
- except ImportError:
15
- return
13
+ if use_cache and importlib.util.find_spec("langchain") is not None:
16
14
  from langchain.cache import SQLiteCache
17
15
  from langchain.globals import set_llm_cache
18
16
 
@@ -25,7 +23,7 @@ def init_lc_cache():
25
23
  )
26
24
 
27
25
 
28
- _cache = {}
26
+ _cache: dict[tuple, Any] = {}
29
27
  _cache_lock = threading.Lock()
30
28
 
31
29
 
chainlit/callbacks.py ADDED
@@ -0,0 +1,362 @@
1
+ import inspect
2
+ from typing import Any, Awaitable, Callable, Dict, List, Optional
3
+
4
+ from fastapi import Request, Response
5
+ from starlette.datastructures import Headers
6
+
7
+ from chainlit.action import Action
8
+ from chainlit.config import config
9
+ from chainlit.context import context
10
+ from chainlit.data.base import BaseDataLayer
11
+ from chainlit.message import Message
12
+ from chainlit.oauth_providers import get_configured_oauth_providers
13
+ from chainlit.step import Step, step
14
+ from chainlit.telemetry import trace
15
+ from chainlit.types import ChatProfile, Starter, ThreadDict
16
+ from chainlit.user import User
17
+ from chainlit.utils import wrap_user_function
18
+
19
+
20
+ @trace
21
+ def password_auth_callback(
22
+ func: Callable[[str, str], Awaitable[Optional[User]]],
23
+ ) -> Callable:
24
+ """
25
+ Framework agnostic decorator to authenticate the user.
26
+
27
+ Args:
28
+ func (Callable[[str, str], Awaitable[Optional[User]]]): The authentication callback to execute. Takes the email and password as parameters.
29
+
30
+ Example:
31
+ @cl.password_auth_callback
32
+ async def password_auth_callback(username: str, password: str) -> Optional[User]:
33
+
34
+ Returns:
35
+ Callable[[str, str], Awaitable[Optional[User]]]: The decorated authentication callback.
36
+ """
37
+
38
+ config.code.password_auth_callback = wrap_user_function(func)
39
+ return func
40
+
41
+
42
+ @trace
43
+ def header_auth_callback(
44
+ func: Callable[[Headers], Awaitable[Optional[User]]],
45
+ ) -> Callable:
46
+ """
47
+ Framework agnostic decorator to authenticate the user via a header
48
+
49
+ Args:
50
+ func (Callable[[Headers], Awaitable[Optional[User]]]): The authentication callback to execute.
51
+
52
+ Example:
53
+ @cl.header_auth_callback
54
+ async def header_auth_callback(headers: Headers) -> Optional[User]:
55
+
56
+ Returns:
57
+ Callable[[Headers], Awaitable[Optional[User]]]: The decorated authentication callback.
58
+ """
59
+
60
+ config.code.header_auth_callback = wrap_user_function(func)
61
+ return func
62
+
63
+
64
+ @trace
65
+ def oauth_callback(
66
+ func: Callable[
67
+ [str, str, Dict[str, str], User, Optional[str]], Awaitable[Optional[User]]
68
+ ],
69
+ ) -> Callable:
70
+ """
71
+ Framework agnostic decorator to authenticate the user via oauth
72
+
73
+ Args:
74
+ func (Callable[[str, str, Dict[str, str], User, Optional[str]], Awaitable[Optional[User]]]): The authentication callback to execute.
75
+
76
+ Example:
77
+ @cl.oauth_callback
78
+ async def oauth_callback(provider_id: str, token: str, raw_user_data: Dict[str, str], default_app_user: User, id_token: Optional[str]) -> Optional[User]:
79
+
80
+ Returns:
81
+ Callable[[str, str, Dict[str, str], User, Optional[str]], Awaitable[Optional[User]]]: The decorated authentication callback.
82
+ """
83
+
84
+ if len(get_configured_oauth_providers()) == 0:
85
+ raise ValueError(
86
+ "You must set the environment variable for at least one oauth provider to use oauth authentication."
87
+ )
88
+
89
+ config.code.oauth_callback = wrap_user_function(func)
90
+ return func
91
+
92
+
93
+ @trace
94
+ def on_logout(func: Callable[[Request, Response], Any]) -> Callable:
95
+ """
96
+ Function called when the user logs out.
97
+ Takes the FastAPI request and response as parameters.
98
+ """
99
+
100
+ config.code.on_logout = wrap_user_function(func)
101
+ return func
102
+
103
+
104
+ @trace
105
+ def on_message(func: Callable) -> Callable:
106
+ """
107
+ Framework agnostic decorator to react to messages coming from the UI.
108
+ The decorated function is called every time a new message is received.
109
+
110
+ Args:
111
+ func (Callable[[Message], Any]): The function to be called when a new message is received. Takes a cl.Message.
112
+
113
+ Returns:
114
+ Callable[[str], Any]: The decorated on_message function.
115
+ """
116
+
117
+ async def with_parent_id(message: Message):
118
+ async with Step(name="on_message", type="run", parent_id=message.id) as s:
119
+ s.input = message.content
120
+ if len(inspect.signature(func).parameters) > 0:
121
+ await func(message)
122
+ else:
123
+ await func()
124
+
125
+ config.code.on_message = wrap_user_function(with_parent_id)
126
+ return func
127
+
128
+
129
+ @trace
130
+ async def send_window_message(data: Any):
131
+ """
132
+ Send custom data to the host window via a window.postMessage event.
133
+
134
+ Args:
135
+ data (Any): The data to send with the event.
136
+ """
137
+ await context.emitter.send_window_message(data)
138
+
139
+
140
+ @trace
141
+ def on_window_message(func: Callable[[str], Any]) -> Callable:
142
+ """
143
+ Hook to react to javascript postMessage events coming from the UI.
144
+
145
+ Args:
146
+ func (Callable[[str], Any]): The function to be called when a window message is received.
147
+ Takes the message content as a string parameter.
148
+
149
+ Returns:
150
+ Callable[[str], Any]: The decorated on_window_message function.
151
+ """
152
+ config.code.on_window_message = wrap_user_function(func)
153
+ return func
154
+
155
+
156
+ @trace
157
+ def on_chat_start(func: Callable) -> Callable:
158
+ """
159
+ Hook to react to the user websocket connection event.
160
+
161
+ Args:
162
+ func (Callable[], Any]): The connection hook to execute.
163
+
164
+ Returns:
165
+ Callable[], Any]: The decorated hook.
166
+ """
167
+
168
+ config.code.on_chat_start = wrap_user_function(
169
+ step(func, name="on_chat_start", type="run"), with_task=True
170
+ )
171
+ return func
172
+
173
+
174
+ @trace
175
+ def on_chat_resume(func: Callable[[ThreadDict], Any]) -> Callable:
176
+ """
177
+ Hook to react to resume websocket connection event.
178
+
179
+ Args:
180
+ func (Callable[], Any]): The connection hook to execute.
181
+
182
+ Returns:
183
+ Callable[], Any]: The decorated hook.
184
+ """
185
+
186
+ config.code.on_chat_resume = wrap_user_function(func, with_task=True)
187
+ return func
188
+
189
+
190
+ @trace
191
+ def set_chat_profiles(
192
+ func: Callable[[Optional["User"]], Awaitable[List["ChatProfile"]]],
193
+ ) -> Callable:
194
+ """
195
+ Programmatic declaration of the available chat profiles (can depend on the User from the session if authentication is setup).
196
+
197
+ Args:
198
+ func (Callable[[Optional["User"]], Awaitable[List["ChatProfile"]]]): The function declaring the chat profiles.
199
+
200
+ Returns:
201
+ Callable[[Optional["User"]], Awaitable[List["ChatProfile"]]]: The decorated function.
202
+ """
203
+
204
+ config.code.set_chat_profiles = wrap_user_function(func)
205
+ return func
206
+
207
+
208
+ @trace
209
+ def set_starters(
210
+ func: Callable[[Optional["User"]], Awaitable[List["Starter"]]],
211
+ ) -> Callable:
212
+ """
213
+ Programmatic declaration of the available starter (can depend on the User from the session if authentication is setup).
214
+
215
+ Args:
216
+ func (Callable[[Optional["User"]], Awaitable[List["Starter"]]]): The function declaring the starters.
217
+
218
+ Returns:
219
+ Callable[[Optional["User"]], Awaitable[List["Starter"]]]: The decorated function.
220
+ """
221
+
222
+ config.code.set_starters = wrap_user_function(func)
223
+ return func
224
+
225
+
226
+ @trace
227
+ def on_chat_end(func: Callable) -> Callable:
228
+ """
229
+ Hook to react to the user websocket disconnect event.
230
+
231
+ Args:
232
+ func (Callable[], Any]): The disconnect hook to execute.
233
+
234
+ Returns:
235
+ Callable[], Any]: The decorated hook.
236
+ """
237
+
238
+ config.code.on_chat_end = wrap_user_function(func, with_task=True)
239
+ return func
240
+
241
+
242
+ @trace
243
+ def on_audio_start(func: Callable) -> Callable:
244
+ """
245
+ Hook to react to the user initiating audio.
246
+
247
+ Returns:
248
+ Callable[], Any]: The decorated hook.
249
+ """
250
+
251
+ config.code.on_audio_start = wrap_user_function(func, with_task=False)
252
+ return func
253
+
254
+
255
+ @trace
256
+ def on_audio_chunk(func: Callable) -> Callable:
257
+ """
258
+ Hook to react to the audio chunks being sent.
259
+
260
+ Args:
261
+ chunk (InputAudioChunk): The audio chunk being sent.
262
+
263
+ Returns:
264
+ Callable[], Any]: The decorated hook.
265
+ """
266
+
267
+ config.code.on_audio_chunk = wrap_user_function(func, with_task=False)
268
+ return func
269
+
270
+
271
+ @trace
272
+ def on_audio_end(func: Callable) -> Callable:
273
+ """
274
+ Hook to react to the audio stream ending. This is called after the last audio chunk is sent.
275
+
276
+ Returns:
277
+ Callable[], Any]: The decorated hook.
278
+ """
279
+
280
+ config.code.on_audio_end = wrap_user_function(
281
+ step(func, name="on_audio_end", type="run"), with_task=True
282
+ )
283
+ return func
284
+
285
+
286
+ @trace
287
+ def author_rename(
288
+ func: Callable[[str], Awaitable[str]],
289
+ ) -> Callable[[str], Awaitable[str]]:
290
+ """
291
+ Useful to rename the author of message to display more friendly author names in the UI.
292
+ Args:
293
+ func (Callable[[str], Awaitable[str]]): The function to be called to rename an author. Takes the original author name as parameter.
294
+
295
+ Returns:
296
+ Callable[[Any, str], Awaitable[Any]]: The decorated function.
297
+ """
298
+
299
+ config.code.author_rename = wrap_user_function(func)
300
+ return func
301
+
302
+
303
+ @trace
304
+ def on_stop(func: Callable) -> Callable:
305
+ """
306
+ Hook to react to the user stopping a thread.
307
+
308
+ Args:
309
+ func (Callable[[], Any]): The stop hook to execute.
310
+
311
+ Returns:
312
+ Callable[[], Any]: The decorated stop hook.
313
+ """
314
+
315
+ config.code.on_stop = wrap_user_function(func)
316
+ return func
317
+
318
+
319
+ def action_callback(name: str) -> Callable:
320
+ """
321
+ Callback to call when an action is clicked in the UI.
322
+
323
+ Args:
324
+ func (Callable[[Action], Any]): The action callback to execute. First parameter is the action.
325
+ """
326
+
327
+ def decorator(func: Callable[[Action], Any]):
328
+ config.code.action_callbacks[name] = wrap_user_function(func, with_task=False)
329
+ return func
330
+
331
+ return decorator
332
+
333
+
334
+ def on_settings_update(
335
+ func: Callable[[Dict[str, Any]], Any],
336
+ ) -> Callable[[Dict[str, Any]], Any]:
337
+ """
338
+ Hook to react to the user changing any settings.
339
+
340
+ Args:
341
+ func (Callable[], Any]): The hook to execute after settings were changed.
342
+
343
+ Returns:
344
+ Callable[], Any]: The decorated hook.
345
+ """
346
+
347
+ config.code.on_settings_update = wrap_user_function(func, with_task=True)
348
+ return func
349
+
350
+
351
+ def data_layer(
352
+ func: Callable[[], BaseDataLayer],
353
+ ) -> Callable[[], BaseDataLayer]:
354
+ """
355
+ Hook to configure custom data layer.
356
+ """
357
+
358
+ # We don't use wrap_user_function here because:
359
+ # 1. We don't need to support async here and;
360
+ # 2. We don't want to change the API for get_data_layer() to be async, everywhere (at this point).
361
+ config.code.data_layer = func
362
+ return func
@@ -0,0 +1,64 @@
1
+ from typing import TYPE_CHECKING, Dict, List
2
+
3
+ from chainlit.context import context
4
+
5
+ if TYPE_CHECKING:
6
+ from chainlit.message import Message
7
+
8
+ chat_contexts: Dict[str, List["Message"]] = {}
9
+
10
+
11
+ class ChatContext:
12
+ def get(self) -> List["Message"]:
13
+ if not context.session:
14
+ return []
15
+
16
+ if context.session.id not in chat_contexts:
17
+ # Create a new chat context
18
+ chat_contexts[context.session.id] = []
19
+
20
+ return chat_contexts[context.session.id].copy()
21
+
22
+ def add(self, message: "Message"):
23
+ if not context.session:
24
+ return
25
+
26
+ if context.session.id not in chat_contexts:
27
+ chat_contexts[context.session.id] = []
28
+
29
+ if message not in chat_contexts[context.session.id]:
30
+ chat_contexts[context.session.id].append(message)
31
+
32
+ return message
33
+
34
+ def remove(self, message: "Message") -> bool:
35
+ if not context.session:
36
+ return False
37
+
38
+ if context.session.id not in chat_contexts:
39
+ return False
40
+
41
+ if message in chat_contexts[context.session.id]:
42
+ chat_contexts[context.session.id].remove(message)
43
+ return True
44
+
45
+ return False
46
+
47
+ def clear(self) -> None:
48
+ if context.session and context.session.id in chat_contexts:
49
+ chat_contexts[context.session.id] = []
50
+
51
+ def to_openai(self):
52
+ messages = []
53
+ for message in self.get():
54
+ if message.type == "assistant_message":
55
+ messages.append({"role": "assistant", "content": message.content})
56
+ elif message.type == "user_message":
57
+ messages.append({"role": "user", "content": message.content})
58
+ else:
59
+ messages.append({"role": "system", "content": message.content})
60
+
61
+ return messages
62
+
63
+
64
+ chat_context = ChatContext()
chainlit/chat_settings.py CHANGED
@@ -1,8 +1,10 @@
1
1
  from typing import List
2
2
 
3
+ from pydantic import Field
4
+ from pydantic.dataclasses import dataclass
5
+
3
6
  from chainlit.context import context
4
7
  from chainlit.input_widget import InputWidget
5
- from pydantic.dataclasses import Field, dataclass
6
8
 
7
9
 
8
10
  @dataclass