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

Potentially problematic release.


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

Files changed (82) hide show
  1. chainlit/__init__.py +58 -56
  2. chainlit/action.py +12 -10
  3. chainlit/{auth.py → auth/__init__.py} +24 -34
  4. chainlit/auth/cookie.py +123 -0
  5. chainlit/auth/jwt.py +37 -0
  6. chainlit/cache.py +4 -6
  7. chainlit/callbacks.py +65 -11
  8. chainlit/chat_context.py +2 -2
  9. chainlit/chat_settings.py +3 -1
  10. chainlit/cli/__init__.py +15 -2
  11. chainlit/config.py +46 -90
  12. chainlit/context.py +4 -3
  13. chainlit/copilot/dist/index.js +8608 -642
  14. chainlit/data/__init__.py +96 -8
  15. chainlit/data/acl.py +3 -2
  16. chainlit/data/base.py +1 -15
  17. chainlit/data/chainlit_data_layer.py +584 -0
  18. chainlit/data/dynamodb.py +7 -4
  19. chainlit/data/literalai.py +4 -6
  20. chainlit/data/sql_alchemy.py +9 -8
  21. chainlit/data/storage_clients/__init__.py +0 -0
  22. chainlit/data/{storage_clients.py → storage_clients/azure.py} +2 -33
  23. chainlit/data/storage_clients/azure_blob.py +80 -0
  24. chainlit/data/storage_clients/base.py +22 -0
  25. chainlit/data/storage_clients/gcs.py +78 -0
  26. chainlit/data/storage_clients/s3.py +49 -0
  27. chainlit/discord/__init__.py +4 -4
  28. chainlit/discord/app.py +2 -1
  29. chainlit/element.py +41 -9
  30. chainlit/emitter.py +37 -16
  31. chainlit/frontend/dist/assets/{DailyMotion-CwoOhIL8.js → DailyMotion-DgRzV5GZ.js} +1 -1
  32. chainlit/frontend/dist/assets/Dataframe-DVgwSMU2.js +22 -0
  33. chainlit/frontend/dist/assets/{Facebook-BhnGXlzq.js → Facebook-C0vx6HWv.js} +1 -1
  34. chainlit/frontend/dist/assets/{FilePlayer-CPSVT6fz.js → FilePlayer-CdhzeHPP.js} +1 -1
  35. chainlit/frontend/dist/assets/{Kaltura-COYaLzsL.js → Kaltura-5iVmeUct.js} +1 -1
  36. chainlit/frontend/dist/assets/{Mixcloud-JdadNiQ5.js → Mixcloud-C2zi77Ex.js} +1 -1
  37. chainlit/frontend/dist/assets/{Mux-CBN7RO2u.js → Mux-Vkebogdf.js} +1 -1
  38. chainlit/frontend/dist/assets/{Preview-CxAFvvjV.js → Preview-DwY_sEIl.js} +1 -1
  39. chainlit/frontend/dist/assets/{SoundCloud-JlgmASWm.js → SoundCloud-CREBXAWo.js} +1 -1
  40. chainlit/frontend/dist/assets/{Streamable-CUWgr6Zw.js → Streamable-B5Lu25uy.js} +1 -1
  41. chainlit/frontend/dist/assets/{Twitch-BiN1HEDM.js → Twitch-y9iKCcM1.js} +1 -1
  42. chainlit/frontend/dist/assets/{Vidyard-qhPmrhDm.js → Vidyard-ClYvcuEu.js} +1 -1
  43. chainlit/frontend/dist/assets/{Vimeo-CrZVSCaT.js → Vimeo-D6HvM2jt.js} +1 -1
  44. chainlit/frontend/dist/assets/Wistia-Cu4zZ2Ci.js +1 -0
  45. chainlit/frontend/dist/assets/{YouTube-DKjw5Hbn.js → YouTube-D10tR6CJ.js} +1 -1
  46. chainlit/frontend/dist/assets/index-CI4qFOt5.js +8665 -0
  47. chainlit/frontend/dist/assets/index-CrrqM0nZ.css +1 -0
  48. chainlit/frontend/dist/assets/{react-plotly-Dpmqg5Sy.js → react-plotly-BpxUS-ab.js} +1 -1
  49. chainlit/frontend/dist/index.html +2 -2
  50. chainlit/haystack/callbacks.py +5 -4
  51. chainlit/input_widget.py +6 -4
  52. chainlit/langchain/callbacks.py +56 -47
  53. chainlit/langflow/__init__.py +1 -0
  54. chainlit/llama_index/callbacks.py +7 -7
  55. chainlit/message.py +8 -10
  56. chainlit/mistralai/__init__.py +3 -2
  57. chainlit/oauth_providers.py +70 -3
  58. chainlit/openai/__init__.py +3 -2
  59. chainlit/secret.py +1 -1
  60. chainlit/server.py +481 -182
  61. chainlit/session.py +7 -5
  62. chainlit/slack/__init__.py +3 -3
  63. chainlit/slack/app.py +3 -2
  64. chainlit/socket.py +89 -112
  65. chainlit/step.py +12 -12
  66. chainlit/sync.py +2 -1
  67. chainlit/teams/__init__.py +3 -3
  68. chainlit/teams/app.py +1 -0
  69. chainlit/translations/en-US.json +2 -1
  70. chainlit/translations/nl-NL.json +229 -0
  71. chainlit/types.py +24 -8
  72. chainlit/user.py +2 -1
  73. chainlit/utils.py +3 -2
  74. chainlit/version.py +3 -2
  75. {chainlit-1.3.1.dist-info → chainlit-2.0.0.dist-info}/METADATA +17 -37
  76. chainlit-2.0.0.dist-info/RECORD +106 -0
  77. chainlit/frontend/dist/assets/Wistia-C891KrBP.js +0 -1
  78. chainlit/frontend/dist/assets/index-CwmincdQ.css +0 -1
  79. chainlit/frontend/dist/assets/index-DLRdQOIx.js +0 -723
  80. chainlit-1.3.1.dist-info/RECORD +0 -96
  81. {chainlit-1.3.1.dist-info → chainlit-2.0.0.dist-info}/WHEEL +0 -0
  82. {chainlit-1.3.1.dist-info → chainlit-2.0.0.dist-info}/entry_points.txt +0 -0
chainlit/__init__.py CHANGED
@@ -14,6 +14,9 @@ if env_found:
14
14
  import asyncio
15
15
  from typing import TYPE_CHECKING, Any, Dict
16
16
 
17
+ from literalai import ChatGeneration, CompletionGeneration, GenerationMessage
18
+ from pydantic.dataclasses import dataclass
19
+
17
20
  import chainlit.input_widget as input_widget
18
21
  from chainlit.action import Action
19
22
  from chainlit.cache import cache
@@ -22,7 +25,8 @@ from chainlit.chat_settings import ChatSettings
22
25
  from chainlit.context import context
23
26
  from chainlit.element import (
24
27
  Audio,
25
- Component,
28
+ CustomElement,
29
+ Dataframe,
26
30
  File,
27
31
  Image,
28
32
  Pdf,
@@ -43,21 +47,21 @@ from chainlit.message import (
43
47
  )
44
48
  from chainlit.step import Step, step
45
49
  from chainlit.sync import make_async, run_sync
46
- from chainlit.types import AudioChunk, ChatProfile, Starter
50
+ from chainlit.types import ChatProfile, InputAudioChunk, OutputAudioChunk, Starter
47
51
  from chainlit.user import PersistedUser, User
48
52
  from chainlit.user_session import user_session
49
53
  from chainlit.utils import make_module_getattr
50
54
  from chainlit.version import __version__
51
- from literalai import ChatGeneration, CompletionGeneration, GenerationMessage
52
- from pydantic.dataclasses import dataclass
53
55
 
54
56
  from .callbacks import (
55
57
  action_callback,
56
58
  author_rename,
59
+ data_layer,
57
60
  header_auth_callback,
58
61
  oauth_callback,
59
62
  on_audio_chunk,
60
63
  on_audio_end,
64
+ on_audio_start,
61
65
  on_chat_end,
62
66
  on_chat_resume,
63
67
  on_chat_start,
@@ -65,7 +69,9 @@ from .callbacks import (
65
69
  on_message,
66
70
  on_settings_update,
67
71
  on_stop,
72
+ on_window_message,
68
73
  password_auth_callback,
74
+ send_window_message,
69
75
  set_chat_profiles,
70
76
  set_starters,
71
77
  )
@@ -111,77 +117,73 @@ __getattr__ = make_module_getattr(
111
117
  )
112
118
 
113
119
  __all__ = [
114
- "__version__",
115
- "ChatProfile",
116
- "Starter",
117
- "user_session",
118
- "chat_context",
119
- "CopilotFunction",
120
- "AudioChunk",
121
120
  "Action",
122
- "User",
123
- "PersistedUser",
121
+ "AskActionMessage",
122
+ "AskFileMessage",
123
+ "AskUserMessage",
124
+ "AsyncLangchainCallbackHandler",
124
125
  "Audio",
126
+ "ChatGeneration",
127
+ "ChatProfile",
128
+ "ChatSettings",
129
+ "CompletionGeneration",
130
+ "CopilotFunction",
131
+ "CustomElement",
132
+ "Dataframe",
133
+ "ErrorMessage",
134
+ "File",
135
+ "GenerationMessage",
136
+ "HaystackAgentCallbackHandler",
137
+ "Image",
138
+ "InputAudioChunk",
139
+ "LangchainCallbackHandler",
140
+ "LlamaIndexCallbackHandler",
141
+ "Message",
142
+ "OutputAudioChunk",
125
143
  "Pdf",
144
+ "PersistedUser",
126
145
  "Plotly",
127
- "Image",
128
- "Text",
129
- "Component",
130
146
  "Pyplot",
131
- "File",
147
+ "Starter",
148
+ "Step",
132
149
  "Task",
133
150
  "TaskList",
134
151
  "TaskStatus",
152
+ "Text",
153
+ "User",
135
154
  "Video",
136
- "ChatSettings",
137
- "input_widget",
138
- "Message",
139
- "ErrorMessage",
140
- "AskUserMessage",
141
- "AskActionMessage",
142
- "AskFileMessage",
143
- "Step",
144
- "step",
145
- "ChatGeneration",
146
- "CompletionGeneration",
147
- "GenerationMessage",
148
- "on_logout",
149
- "on_chat_start",
150
- "on_chat_end",
151
- "on_chat_resume",
152
- "on_stop",
155
+ "__version__",
153
156
  "action_callback",
154
157
  "author_rename",
155
- "on_settings_update",
156
- "password_auth_callback",
157
- "header_auth_callback",
158
- "sleep",
159
- "run_sync",
160
- "make_async",
161
158
  "cache",
159
+ "chat_context",
162
160
  "context",
163
- "LangchainCallbackHandler",
164
- "AsyncLangchainCallbackHandler",
165
- "LlamaIndexCallbackHandler",
166
- "HaystackAgentCallbackHandler",
167
- "instrument_openai",
168
- "instrument_mistralai",
169
- "password_auth_callback",
161
+ "data_layer",
170
162
  "header_auth_callback",
163
+ "input_widget",
164
+ "instrument_mistralai",
165
+ "instrument_openai",
166
+ "make_async",
171
167
  "oauth_callback",
168
+ "on_audio_chunk",
169
+ "on_audio_end",
170
+ "on_audio_start",
171
+ "on_chat_end",
172
+ "on_chat_resume",
173
+ "on_chat_start",
172
174
  "on_logout",
173
175
  "on_message",
174
- "on_chat_start",
175
- "on_chat_resume",
176
+ "on_settings_update",
177
+ "on_stop",
178
+ "on_window_message",
179
+ "password_auth_callback",
180
+ "run_sync",
181
+ "send_window_message",
176
182
  "set_chat_profiles",
177
183
  "set_starters",
178
- "on_chat_end",
179
- "on_audio_chunk",
180
- "on_audio_end",
181
- "author_rename",
182
- "on_stop",
183
- "action_callback",
184
- "on_settings_update",
184
+ "sleep",
185
+ "step",
186
+ "user_session",
185
187
  ]
186
188
 
187
189
 
chainlit/action.py CHANGED
@@ -1,28 +1,30 @@
1
1
  import uuid
2
- from typing import Optional
2
+ from typing import Dict, Optional
3
+
4
+ from dataclasses_json import DataClassJsonMixin
5
+ from pydantic import Field
6
+ from pydantic.dataclasses import dataclass
3
7
 
4
8
  from chainlit.context import context
5
9
  from chainlit.telemetry import trace_event
6
- from dataclasses_json import DataClassJsonMixin
7
- from pydantic.dataclasses import Field, dataclass
8
10
 
9
11
 
10
12
  @dataclass
11
13
  class Action(DataClassJsonMixin):
12
14
  # Name of the action, this should be used in the action_callback
13
15
  name: str
14
- # The value associated with the action. This is useful to differentiate between multiple actions with the same name.
15
- value: str
16
- # The label of the action. This is what the user will see. If not provided the name will be used.
16
+ # The parameters to call this action with.
17
+ payload: Dict
18
+ # The label of the action. This is what the user will see.
17
19
  label: str = ""
18
- # The description of the action. This is what the user will see when they hover the action.
19
- description: str = ""
20
+ # The tooltip of the action button. This is what the user will see when they hover the action.
21
+ tooltip: str = ""
22
+ # The lucid icon name for this action.
23
+ icon: Optional[str] = None
20
24
  # This should not be set manually, only used internally.
21
25
  forId: Optional[str] = None
22
26
  # The ID of the action
23
27
  id: str = Field(default_factory=lambda: str(uuid.uuid4()))
24
- # Show the action in a drawer menu
25
- collapsed: bool = False
26
28
 
27
29
  def __post_init__(self) -> None:
28
30
  trace_event(f"init {self.__class__.__name__}")
@@ -1,20 +1,16 @@
1
1
  import os
2
- from datetime import datetime, timedelta
3
- from typing import Any, Dict
4
2
 
5
- import jwt
3
+ from fastapi import Depends, HTTPException
4
+
6
5
  from chainlit.config import config
7
6
  from chainlit.data import get_data_layer
7
+ from chainlit.logger import logger
8
8
  from chainlit.oauth_providers import get_configured_oauth_providers
9
- from chainlit.user import User
10
- from fastapi import Depends, HTTPException
11
- from fastapi.security import OAuth2PasswordBearer
12
-
13
- reuseable_oauth = OAuth2PasswordBearer(tokenUrl="/login", auto_error=False)
14
9
 
10
+ from .cookie import OAuth2PasswordBearerWithCookie
11
+ from .jwt import create_jwt, decode_jwt, get_jwt_secret
15
12
 
16
- def get_jwt_secret():
17
- return os.environ.get("CHAINLIT_AUTH_SECRET")
13
+ reuseable_oauth = OAuth2PasswordBearerWithCookie(tokenUrl="/login", auto_error=False)
18
14
 
19
15
 
20
16
  def ensure_jwt_secret():
@@ -45,45 +41,36 @@ def get_configuration():
45
41
  "oauthProviders": (
46
42
  get_configured_oauth_providers() if is_oauth_enabled() else []
47
43
  ),
44
+ "default_theme": config.ui.default_theme,
48
45
  }
49
46
 
50
47
 
51
- def create_jwt(data: User) -> str:
52
- to_encode: Dict[str, Any] = data.to_dict()
53
- to_encode.update(
54
- {
55
- "exp": datetime.utcnow() + timedelta(minutes=60 * 24 * 15), # 15 days
56
- }
57
- )
58
- encoded_jwt = jwt.encode(to_encode, get_jwt_secret(), algorithm="HS256")
59
- return encoded_jwt
60
-
61
-
62
48
  async def authenticate_user(token: str = Depends(reuseable_oauth)):
63
49
  try:
64
- dict = jwt.decode(
65
- token,
66
- get_jwt_secret(),
67
- algorithms=["HS256"],
68
- options={"verify_signature": True},
69
- )
70
- del dict["exp"]
71
- user = User(**dict)
72
- except Exception:
73
- raise HTTPException(status_code=401, detail="Invalid authentication token")
50
+ user = decode_jwt(token)
51
+ except Exception as e:
52
+ raise HTTPException(
53
+ status_code=401, detail="Invalid authentication token"
54
+ ) from e
55
+
74
56
  if data_layer := get_data_layer():
57
+ # Get or create persistent user if we've a data layer available.
75
58
  try:
76
59
  persisted_user = await data_layer.get_user(user.identifier)
77
60
  if persisted_user is None:
78
61
  persisted_user = await data_layer.create_user(user)
79
- except Exception:
62
+ assert persisted_user
63
+ except Exception as e:
64
+ logger.exception("Unable to get persisted_user from data layer: %s", e)
80
65
  return user
81
66
 
82
67
  if user and user.display_name:
68
+ # Copy ephemeral display_name from authenticated user to persistent user.
83
69
  persisted_user.display_name = user.display_name
70
+
84
71
  return persisted_user
85
- else:
86
- return user
72
+
73
+ return user
87
74
 
88
75
 
89
76
  async def get_current_user(token: str = Depends(reuseable_oauth)):
@@ -91,3 +78,6 @@ async def get_current_user(token: str = Depends(reuseable_oauth)):
91
78
  return None
92
79
 
93
80
  return await authenticate_user(token)
81
+
82
+
83
+ __all__ = ["create_jwt", "get_configuration", "get_current_user"]
@@ -0,0 +1,123 @@
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 (
17
+ _cookie_samesite
18
+ in [
19
+ "lax",
20
+ "strict",
21
+ "none",
22
+ ]
23
+ ), "Invalid value for CHAINLIT_COOKIE_SAMESITE. Must be one of 'lax', 'strict' or 'none'."
24
+ _cookie_secure = _cookie_samesite == "none"
25
+
26
+ _auth_cookie_lifetime = 60 * 60 # 1 hour
27
+ _state_cookie_lifetime = 3 * 60 # 3m
28
+ _auth_cookie_name = "access_token"
29
+ _state_cookie_name = "oauth_state"
30
+
31
+
32
+ class OAuth2PasswordBearerWithCookie(SecurityBase):
33
+ """
34
+ OAuth2 password flow with cookie support with fallback to bearer token.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ tokenUrl: str,
40
+ scheme_name: Optional[str] = None,
41
+ auto_error: bool = True,
42
+ ):
43
+ self.tokenUrl = tokenUrl
44
+ self.scheme_name = scheme_name or self.__class__.__name__
45
+ self.auto_error = auto_error
46
+
47
+ async def __call__(self, request: Request) -> Optional[str]:
48
+ # First try to get the token from the cookie
49
+ token = request.cookies.get(_auth_cookie_name)
50
+
51
+ # If no cookie, try the Authorization header as fallback
52
+ if not token:
53
+ # TODO: Only bother to check if cookie auth is explicitly disabled.
54
+ authorization = request.headers.get("Authorization")
55
+ if authorization:
56
+ scheme, token = get_authorization_scheme_param(authorization)
57
+ if scheme.lower() != "bearer":
58
+ if self.auto_error:
59
+ raise HTTPException(
60
+ status_code=HTTP_401_UNAUTHORIZED,
61
+ detail="Invalid authentication credentials",
62
+ headers={"WWW-Authenticate": "Bearer"},
63
+ )
64
+ else:
65
+ return None
66
+ else:
67
+ if self.auto_error:
68
+ raise HTTPException(
69
+ status_code=HTTP_401_UNAUTHORIZED,
70
+ detail="Not authenticated",
71
+ headers={"WWW-Authenticate": "Bearer"},
72
+ )
73
+ else:
74
+ return None
75
+
76
+ return token
77
+
78
+
79
+ def set_auth_cookie(response: Response, token: str):
80
+ """
81
+ Helper function to set the authentication cookie with secure parameters
82
+ """
83
+
84
+ response.set_cookie(
85
+ key=_auth_cookie_name,
86
+ value=token,
87
+ httponly=True,
88
+ secure=_cookie_secure,
89
+ samesite=_cookie_samesite,
90
+ max_age=_auth_cookie_lifetime,
91
+ )
92
+
93
+
94
+ def clear_auth_cookie(response: Response):
95
+ """
96
+ Helper function to clear the authentication cookie
97
+ """
98
+ response.delete_cookie(key=_auth_cookie_name, path="/")
99
+
100
+
101
+ def set_oauth_state_cookie(response: Response, token: str):
102
+ response.set_cookie(
103
+ _state_cookie_name,
104
+ token,
105
+ httponly=True,
106
+ samesite=_cookie_samesite,
107
+ secure=_cookie_secure,
108
+ max_age=_state_cookie_lifetime,
109
+ )
110
+
111
+
112
+ def validate_oauth_state_cookie(request: Request, state: str):
113
+ """Check the state from the oauth provider against the browser cookie."""
114
+
115
+ oauth_state = request.cookies.get(_state_cookie_name)
116
+
117
+ if oauth_state != state:
118
+ raise Exception("oauth state does not correspond")
119
+
120
+
121
+ def clear_oauth_state_cookie(response: Response):
122
+ """Oauth complete, delete state token."""
123
+ response.delete_cookie(_state_cookie_name) # Do we set path here?
chainlit/auth/jwt.py ADDED
@@ -0,0 +1,37 @@
1
+ import datetime
2
+ import os
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.datetime.utcnow()
20
+ + datetime.timedelta(seconds=config.project.user_session_timeout),
21
+ }
22
+ )
23
+ secret = get_jwt_secret()
24
+ assert secret
25
+ encoded_jwt = pyjwt.encode(to_encode, secret, algorithm="HS256")
26
+ return encoded_jwt
27
+
28
+
29
+ def decode_jwt(token: str) -> User:
30
+ dict = pyjwt.decode(
31
+ token,
32
+ get_jwt_secret(),
33
+ algorithms=["HS256"],
34
+ options={"verify_signature": True},
35
+ )
36
+ del dict["exp"]
37
+ 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 CHANGED
@@ -1,8 +1,13 @@
1
1
  import inspect
2
2
  from typing import Any, Awaitable, Callable, Dict, List, Optional
3
3
 
4
+ from fastapi import Request, Response
5
+ from starlette.datastructures import Headers
6
+
4
7
  from chainlit.action import Action
5
8
  from chainlit.config import config
9
+ from chainlit.context import context
10
+ from chainlit.data.base import BaseDataLayer
6
11
  from chainlit.message import Message
7
12
  from chainlit.oauth_providers import get_configured_oauth_providers
8
13
  from chainlit.step import Step, step
@@ -10,13 +15,11 @@ from chainlit.telemetry import trace
10
15
  from chainlit.types import ChatProfile, Starter, ThreadDict
11
16
  from chainlit.user import User
12
17
  from chainlit.utils import wrap_user_function
13
- from fastapi import Request, Response
14
- from starlette.datastructures import Headers
15
18
 
16
19
 
17
20
  @trace
18
21
  def password_auth_callback(
19
- func: Callable[[str, str], Awaitable[Optional[User]]]
22
+ func: Callable[[str, str], Awaitable[Optional[User]]],
20
23
  ) -> Callable:
21
24
  """
22
25
  Framework agnostic decorator to authenticate the user.
@@ -38,7 +41,7 @@ def password_auth_callback(
38
41
 
39
42
  @trace
40
43
  def header_auth_callback(
41
- func: Callable[[Headers], Awaitable[Optional[User]]]
44
+ func: Callable[[Headers], Awaitable[Optional[User]]],
42
45
  ) -> Callable:
43
46
  """
44
47
  Framework agnostic decorator to authenticate the user via a header
@@ -123,6 +126,33 @@ def on_message(func: Callable) -> Callable:
123
126
  return func
124
127
 
125
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
+
126
156
  @trace
127
157
  def on_chat_start(func: Callable) -> Callable:
128
158
  """
@@ -177,7 +207,7 @@ def set_chat_profiles(
177
207
 
178
208
  @trace
179
209
  def set_starters(
180
- func: Callable[[Optional["User"]], Awaitable[List["Starter"]]]
210
+ func: Callable[[Optional["User"]], Awaitable[List["Starter"]]],
181
211
  ) -> Callable:
182
212
  """
183
213
  Programmatic declaration of the available starter (can depend on the User from the session if authentication is setup).
@@ -209,13 +239,26 @@ def on_chat_end(func: Callable) -> Callable:
209
239
  return func
210
240
 
211
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
+
212
255
  @trace
213
256
  def on_audio_chunk(func: Callable) -> Callable:
214
257
  """
215
258
  Hook to react to the audio chunks being sent.
216
259
 
217
260
  Args:
218
- chunk (AudioChunk): The audio chunk being sent.
261
+ chunk (InputAudioChunk): The audio chunk being sent.
219
262
 
220
263
  Returns:
221
264
  Callable[], Any]: The decorated hook.
@@ -230,9 +273,6 @@ def on_audio_end(func: Callable) -> Callable:
230
273
  """
231
274
  Hook to react to the audio stream ending. This is called after the last audio chunk is sent.
232
275
 
233
- Args:
234
- elements ([List[Element]): The files that were uploaded before starting the audio stream (if any).
235
-
236
276
  Returns:
237
277
  Callable[], Any]: The decorated hook.
238
278
  """
@@ -245,7 +285,7 @@ def on_audio_end(func: Callable) -> Callable:
245
285
 
246
286
  @trace
247
287
  def author_rename(
248
- func: Callable[[str], Awaitable[str]]
288
+ func: Callable[[str], Awaitable[str]],
249
289
  ) -> Callable[[str], Awaitable[str]]:
250
290
  """
251
291
  Useful to rename the author of message to display more friendly author names in the UI.
@@ -285,7 +325,7 @@ def action_callback(name: str) -> Callable:
285
325
  """
286
326
 
287
327
  def decorator(func: Callable[[Action], Any]):
288
- config.code.action_callbacks[name] = wrap_user_function(func, with_task=True)
328
+ config.code.action_callbacks[name] = wrap_user_function(func, with_task=False)
289
329
  return func
290
330
 
291
331
  return decorator
@@ -306,3 +346,17 @@ def on_settings_update(
306
346
 
307
347
  config.code.on_settings_update = wrap_user_function(func, with_task=True)
308
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
chainlit/chat_context.py CHANGED
@@ -25,10 +25,10 @@ class ChatContext:
25
25
 
26
26
  if context.session.id not in chat_contexts:
27
27
  chat_contexts[context.session.id] = []
28
-
28
+
29
29
  if message not in chat_contexts[context.session.id]:
30
30
  chat_contexts[context.session.id].append(message)
31
-
31
+
32
32
  return message
33
33
 
34
34
  def remove(self, message: "Message") -> bool:
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