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.
- chainlit/__init__.py +98 -279
- chainlit/_utils.py +8 -0
- chainlit/action.py +12 -10
- chainlit/{auth.py → auth/__init__.py} +28 -36
- chainlit/auth/cookie.py +122 -0
- chainlit/auth/jwt.py +39 -0
- chainlit/cache.py +4 -6
- chainlit/callbacks.py +362 -0
- chainlit/chat_context.py +64 -0
- chainlit/chat_settings.py +3 -1
- chainlit/cli/__init__.py +77 -8
- chainlit/config.py +181 -101
- chainlit/context.py +42 -13
- chainlit/copilot/dist/index.js +8750 -903
- chainlit/data/__init__.py +101 -416
- chainlit/data/acl.py +6 -2
- chainlit/data/base.py +107 -0
- chainlit/data/chainlit_data_layer.py +608 -0
- chainlit/data/dynamodb.py +590 -0
- chainlit/data/literalai.py +500 -0
- chainlit/data/sql_alchemy.py +721 -0
- chainlit/data/storage_clients/__init__.py +0 -0
- chainlit/data/storage_clients/azure.py +81 -0
- chainlit/data/storage_clients/azure_blob.py +89 -0
- chainlit/data/storage_clients/base.py +26 -0
- chainlit/data/storage_clients/gcs.py +88 -0
- chainlit/data/storage_clients/s3.py +75 -0
- chainlit/data/utils.py +29 -0
- chainlit/discord/__init__.py +6 -0
- chainlit/discord/app.py +354 -0
- chainlit/element.py +91 -33
- chainlit/emitter.py +80 -29
- chainlit/frontend/dist/assets/DailyMotion-C_XC7xJI.js +1 -0
- chainlit/frontend/dist/assets/Dataframe-Cs4l4hA1.js +22 -0
- chainlit/frontend/dist/assets/Facebook-CUeCH7hk.js +1 -0
- chainlit/frontend/dist/assets/FilePlayer-CB-fYkx8.js +1 -0
- chainlit/frontend/dist/assets/Kaltura-YX6qaq72.js +1 -0
- chainlit/frontend/dist/assets/Mixcloud-DGV0ldjP.js +1 -0
- chainlit/frontend/dist/assets/Mux-CmRss5oc.js +1 -0
- chainlit/frontend/dist/assets/Preview-DBVJn7-H.js +1 -0
- chainlit/frontend/dist/assets/SoundCloud-qLUb18oY.js +1 -0
- chainlit/frontend/dist/assets/Streamable-BvYP7bFp.js +1 -0
- chainlit/frontend/dist/assets/Twitch-CTHt-sGZ.js +1 -0
- chainlit/frontend/dist/assets/Vidyard-B-0mCJbm.js +1 -0
- chainlit/frontend/dist/assets/Vimeo-Dnp7ri8q.js +1 -0
- chainlit/frontend/dist/assets/Wistia-DW0x_UBn.js +1 -0
- chainlit/frontend/dist/assets/YouTube--98FipvA.js +1 -0
- chainlit/frontend/dist/assets/index-D71nZ46o.js +8665 -0
- chainlit/frontend/dist/assets/index-g8LTJwwr.css +1 -0
- chainlit/frontend/dist/assets/react-plotly-Cn_BQTQw.js +3484 -0
- chainlit/frontend/dist/index.html +2 -4
- chainlit/haystack/callbacks.py +4 -7
- chainlit/input_widget.py +8 -4
- chainlit/langchain/callbacks.py +103 -68
- chainlit/langflow/__init__.py +1 -0
- chainlit/llama_index/callbacks.py +65 -40
- chainlit/markdown.py +22 -6
- chainlit/message.py +54 -56
- chainlit/mistralai/__init__.py +50 -0
- chainlit/oauth_providers.py +266 -8
- chainlit/openai/__init__.py +10 -18
- chainlit/secret.py +1 -1
- chainlit/server.py +789 -228
- chainlit/session.py +108 -90
- chainlit/slack/__init__.py +6 -0
- chainlit/slack/app.py +397 -0
- chainlit/socket.py +199 -116
- chainlit/step.py +141 -89
- chainlit/sync.py +2 -1
- chainlit/teams/__init__.py +6 -0
- chainlit/teams/app.py +338 -0
- chainlit/translations/bn.json +235 -0
- chainlit/translations/en-US.json +83 -4
- chainlit/translations/gu.json +235 -0
- chainlit/translations/he-IL.json +235 -0
- chainlit/translations/hi.json +235 -0
- chainlit/translations/kn.json +235 -0
- chainlit/translations/ml.json +235 -0
- chainlit/translations/mr.json +235 -0
- chainlit/translations/nl-NL.json +233 -0
- chainlit/translations/ta.json +235 -0
- chainlit/translations/te.json +235 -0
- chainlit/translations/zh-CN.json +233 -0
- chainlit/translations.py +60 -0
- chainlit/types.py +133 -28
- chainlit/user.py +14 -3
- chainlit/user_session.py +6 -3
- chainlit/utils.py +52 -5
- chainlit/version.py +3 -2
- {chainlit-1.0.401.dist-info → chainlit-2.0.3.dist-info}/METADATA +48 -50
- chainlit-2.0.3.dist-info/RECORD +106 -0
- chainlit/cli/utils.py +0 -24
- chainlit/frontend/dist/assets/index-9711593e.js +0 -723
- chainlit/frontend/dist/assets/index-d088547c.css +0 -1
- chainlit/frontend/dist/assets/react-plotly-d8762cc2.js +0 -3602
- chainlit/playground/__init__.py +0 -2
- chainlit/playground/config.py +0 -40
- chainlit/playground/provider.py +0 -108
- chainlit/playground/providers/__init__.py +0 -13
- chainlit/playground/providers/anthropic.py +0 -118
- chainlit/playground/providers/huggingface.py +0 -75
- chainlit/playground/providers/langchain.py +0 -89
- chainlit/playground/providers/openai.py +0 -408
- chainlit/playground/providers/vertexai.py +0 -171
- chainlit/translations/pt-BR.json +0 -155
- chainlit-1.0.401.dist-info/RECORD +0 -66
- /chainlit/copilot/dist/assets/{logo_dark-2a3cf740.svg → logo_dark-IkGJ_IwC.svg} +0 -0
- /chainlit/copilot/dist/assets/{logo_light-b078e7bc.svg → logo_light-Bb_IPh6r.svg} +0 -0
- /chainlit/frontend/dist/assets/{logo_dark-2a3cf740.svg → logo_dark-IkGJ_IwC.svg} +0 -0
- /chainlit/frontend/dist/assets/{logo_light-b078e7bc.svg → logo_light-Bb_IPh6r.svg} +0 -0
- {chainlit-1.0.401.dist-info → chainlit-2.0.3.dist-info}/WHEEL +0 -0
- {chainlit-1.0.401.dist-info → chainlit-2.0.3.dist-info}/entry_points.txt +0 -0
chainlit/auth/cookie.py
ADDED
|
@@ -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
|
chainlit/chat_context.py
ADDED
|
@@ -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