chainlit 2.7.0__py3-none-any.whl → 2.7.1__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-2.7.0.dist-info → chainlit-2.7.1.dist-info}/METADATA +1 -1
- chainlit-2.7.1.dist-info/RECORD +4 -0
- chainlit/__init__.py +0 -207
- chainlit/__main__.py +0 -4
- chainlit/_utils.py +0 -8
- chainlit/action.py +0 -33
- chainlit/auth/__init__.py +0 -95
- chainlit/auth/cookie.py +0 -197
- chainlit/auth/jwt.py +0 -42
- chainlit/cache.py +0 -45
- chainlit/callbacks.py +0 -433
- chainlit/chat_context.py +0 -64
- chainlit/chat_settings.py +0 -34
- chainlit/cli/__init__.py +0 -235
- chainlit/config.py +0 -621
- chainlit/context.py +0 -112
- chainlit/data/__init__.py +0 -111
- chainlit/data/acl.py +0 -19
- chainlit/data/base.py +0 -107
- chainlit/data/chainlit_data_layer.py +0 -687
- chainlit/data/dynamodb.py +0 -616
- chainlit/data/literalai.py +0 -501
- chainlit/data/sql_alchemy.py +0 -741
- chainlit/data/storage_clients/__init__.py +0 -0
- chainlit/data/storage_clients/azure.py +0 -84
- chainlit/data/storage_clients/azure_blob.py +0 -94
- chainlit/data/storage_clients/base.py +0 -28
- chainlit/data/storage_clients/gcs.py +0 -101
- chainlit/data/storage_clients/s3.py +0 -88
- chainlit/data/utils.py +0 -29
- chainlit/discord/__init__.py +0 -6
- chainlit/discord/app.py +0 -364
- chainlit/element.py +0 -454
- chainlit/emitter.py +0 -450
- chainlit/hello.py +0 -12
- chainlit/input_widget.py +0 -182
- chainlit/langchain/__init__.py +0 -6
- chainlit/langchain/callbacks.py +0 -682
- chainlit/langflow/__init__.py +0 -25
- chainlit/llama_index/__init__.py +0 -6
- chainlit/llama_index/callbacks.py +0 -206
- chainlit/logger.py +0 -16
- chainlit/markdown.py +0 -57
- chainlit/mcp.py +0 -99
- chainlit/message.py +0 -619
- chainlit/mistralai/__init__.py +0 -50
- chainlit/oauth_providers.py +0 -835
- chainlit/openai/__init__.py +0 -53
- chainlit/py.typed +0 -0
- chainlit/secret.py +0 -9
- chainlit/semantic_kernel/__init__.py +0 -111
- chainlit/server.py +0 -1616
- chainlit/session.py +0 -304
- chainlit/sidebar.py +0 -55
- chainlit/slack/__init__.py +0 -6
- chainlit/slack/app.py +0 -427
- chainlit/socket.py +0 -381
- chainlit/step.py +0 -490
- chainlit/sync.py +0 -43
- chainlit/teams/__init__.py +0 -6
- chainlit/teams/app.py +0 -348
- chainlit/translations/bn.json +0 -214
- chainlit/translations/el-GR.json +0 -214
- chainlit/translations/en-US.json +0 -214
- chainlit/translations/fr-FR.json +0 -214
- chainlit/translations/gu.json +0 -214
- chainlit/translations/he-IL.json +0 -214
- chainlit/translations/hi.json +0 -214
- chainlit/translations/ja.json +0 -214
- chainlit/translations/kn.json +0 -214
- chainlit/translations/ml.json +0 -214
- chainlit/translations/mr.json +0 -214
- chainlit/translations/nl.json +0 -214
- chainlit/translations/ta.json +0 -214
- chainlit/translations/te.json +0 -214
- chainlit/translations/zh-CN.json +0 -214
- chainlit/translations.py +0 -60
- chainlit/types.py +0 -334
- chainlit/user.py +0 -43
- chainlit/user_session.py +0 -153
- chainlit/utils.py +0 -173
- chainlit/version.py +0 -8
- chainlit-2.7.0.dist-info/RECORD +0 -84
- {chainlit-2.7.0.dist-info → chainlit-2.7.1.dist-info}/WHEEL +0 -0
- {chainlit-2.7.0.dist-info → chainlit-2.7.1.dist-info}/entry_points.txt +0 -0
chainlit/types.py
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import (
|
|
6
|
-
TYPE_CHECKING,
|
|
7
|
-
Any,
|
|
8
|
-
Dict,
|
|
9
|
-
Generic,
|
|
10
|
-
List,
|
|
11
|
-
Literal,
|
|
12
|
-
Optional,
|
|
13
|
-
Protocol,
|
|
14
|
-
TypedDict,
|
|
15
|
-
TypeVar,
|
|
16
|
-
Union,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from chainlit.element import ElementDict
|
|
21
|
-
from chainlit.step import StepDict
|
|
22
|
-
|
|
23
|
-
from dataclasses_json import DataClassJsonMixin
|
|
24
|
-
from pydantic import BaseModel
|
|
25
|
-
from pydantic.dataclasses import dataclass
|
|
26
|
-
|
|
27
|
-
InputWidgetType = Literal[
|
|
28
|
-
"switch", "slider", "select", "textinput", "tags", "numberinput"
|
|
29
|
-
]
|
|
30
|
-
ToastType = Literal["info", "success", "warning", "error"]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ThreadDict(TypedDict):
|
|
34
|
-
id: str
|
|
35
|
-
createdAt: str
|
|
36
|
-
name: Optional[str]
|
|
37
|
-
userId: Optional[str]
|
|
38
|
-
userIdentifier: Optional[str]
|
|
39
|
-
tags: Optional[List[str]]
|
|
40
|
-
metadata: Optional[Dict]
|
|
41
|
-
steps: List[StepDict]
|
|
42
|
-
elements: Optional[List[ElementDict]]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Pagination(BaseModel):
|
|
46
|
-
first: int
|
|
47
|
-
cursor: Optional[str] = None
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class ThreadFilter(BaseModel):
|
|
51
|
-
feedback: Literal[0, 1] | None = None
|
|
52
|
-
userId: str | None = None
|
|
53
|
-
search: str | None = None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@dataclass
|
|
57
|
-
class PageInfo:
|
|
58
|
-
hasNextPage: bool
|
|
59
|
-
startCursor: Optional[str]
|
|
60
|
-
endCursor: Optional[str]
|
|
61
|
-
|
|
62
|
-
def to_dict(self):
|
|
63
|
-
return {
|
|
64
|
-
"hasNextPage": self.hasNextPage,
|
|
65
|
-
"startCursor": self.startCursor,
|
|
66
|
-
"endCursor": self.endCursor,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@classmethod
|
|
70
|
-
def from_dict(cls, page_info_dict: Dict) -> PageInfo:
|
|
71
|
-
hasNextPage = page_info_dict.get("hasNextPage", False)
|
|
72
|
-
startCursor = page_info_dict.get("startCursor", None)
|
|
73
|
-
endCursor = page_info_dict.get("endCursor", None)
|
|
74
|
-
return cls(
|
|
75
|
-
hasNextPage=hasNextPage, startCursor=startCursor, endCursor=endCursor
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
T = TypeVar("T", covariant=True)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class HasFromDict(Protocol[T]):
|
|
83
|
-
@classmethod
|
|
84
|
-
def from_dict(cls, obj_dict: Any) -> T:
|
|
85
|
-
raise NotImplementedError
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@dataclass
|
|
89
|
-
class PaginatedResponse(Generic[T]):
|
|
90
|
-
pageInfo: PageInfo
|
|
91
|
-
data: List[T]
|
|
92
|
-
|
|
93
|
-
def to_dict(self):
|
|
94
|
-
return {
|
|
95
|
-
"pageInfo": self.pageInfo.to_dict(),
|
|
96
|
-
"data": [
|
|
97
|
-
(d.to_dict() if hasattr(d, "to_dict") and callable(d.to_dict) else d)
|
|
98
|
-
for d in self.data
|
|
99
|
-
],
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@classmethod
|
|
103
|
-
def from_dict(
|
|
104
|
-
cls, paginated_response_dict: Dict, the_class: HasFromDict[T]
|
|
105
|
-
) -> PaginatedResponse[T]:
|
|
106
|
-
pageInfo = PageInfo.from_dict(paginated_response_dict.get("pageInfo", {}))
|
|
107
|
-
|
|
108
|
-
data = [the_class.from_dict(d) for d in paginated_response_dict.get("data", [])]
|
|
109
|
-
|
|
110
|
-
return cls(pageInfo=pageInfo, data=data)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@dataclass
|
|
114
|
-
class FileSpec(DataClassJsonMixin):
|
|
115
|
-
accept: Union[List[str], Dict[str, List[str]]]
|
|
116
|
-
max_files: int
|
|
117
|
-
max_size_mb: int
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@dataclass
|
|
121
|
-
class ActionSpec(DataClassJsonMixin):
|
|
122
|
-
keys: List[str]
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
@dataclass
|
|
126
|
-
class AskSpec(DataClassJsonMixin):
|
|
127
|
-
"""Specification for asking the user."""
|
|
128
|
-
|
|
129
|
-
timeout: int
|
|
130
|
-
type: Literal["text", "file", "action", "element"]
|
|
131
|
-
step_id: str
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@dataclass
|
|
135
|
-
class AskFileSpec(FileSpec, AskSpec, DataClassJsonMixin):
|
|
136
|
-
"""Specification for asking the user a file."""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
@dataclass
|
|
140
|
-
class AskActionSpec(ActionSpec, AskSpec, DataClassJsonMixin):
|
|
141
|
-
"""Specification for asking the user an action"""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
@dataclass
|
|
145
|
-
class AskElementSpec(AskSpec, DataClassJsonMixin):
|
|
146
|
-
"""Specification for asking the user a custom element"""
|
|
147
|
-
|
|
148
|
-
element_id: str
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
class FileReference(TypedDict):
|
|
152
|
-
id: str
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class FileDict(TypedDict):
|
|
156
|
-
id: str
|
|
157
|
-
name: str
|
|
158
|
-
path: Path
|
|
159
|
-
size: int
|
|
160
|
-
type: str
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class MessagePayload(TypedDict):
|
|
164
|
-
message: StepDict
|
|
165
|
-
fileReferences: Optional[List[FileReference]]
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class InputAudioChunkPayload(TypedDict):
|
|
169
|
-
isStart: bool
|
|
170
|
-
mimeType: str
|
|
171
|
-
elapsedTime: float
|
|
172
|
-
data: bytes
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
@dataclass
|
|
176
|
-
class InputAudioChunk:
|
|
177
|
-
isStart: bool
|
|
178
|
-
mimeType: str
|
|
179
|
-
elapsedTime: float
|
|
180
|
-
data: bytes
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class OutputAudioChunk(TypedDict):
|
|
184
|
-
track: str
|
|
185
|
-
mimeType: str
|
|
186
|
-
data: bytes
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
@dataclass
|
|
190
|
-
class AskFileResponse:
|
|
191
|
-
id: str
|
|
192
|
-
name: str
|
|
193
|
-
path: str
|
|
194
|
-
size: int
|
|
195
|
-
type: str
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
class AskActionResponse(TypedDict):
|
|
199
|
-
name: str
|
|
200
|
-
payload: Dict
|
|
201
|
-
label: str
|
|
202
|
-
tooltip: str
|
|
203
|
-
forId: str
|
|
204
|
-
id: str
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
class AskElementResponse(TypedDict, total=False):
|
|
208
|
-
submitted: bool
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
class UpdateThreadRequest(BaseModel):
|
|
212
|
-
threadId: str
|
|
213
|
-
name: str
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
class DeleteThreadRequest(BaseModel):
|
|
217
|
-
threadId: str
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
class DeleteFeedbackRequest(BaseModel):
|
|
221
|
-
feedbackId: str
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
class GetThreadsRequest(BaseModel):
|
|
225
|
-
pagination: Pagination
|
|
226
|
-
filter: ThreadFilter
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
class CallActionRequest(BaseModel):
|
|
230
|
-
action: Dict
|
|
231
|
-
sessionId: str
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
class ConnectStdioMCPRequest(BaseModel):
|
|
235
|
-
sessionId: str
|
|
236
|
-
clientType: Literal["stdio"]
|
|
237
|
-
name: str
|
|
238
|
-
fullCommand: str
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
class ConnectSseMCPRequest(BaseModel):
|
|
242
|
-
sessionId: str
|
|
243
|
-
clientType: Literal["sse"]
|
|
244
|
-
name: str
|
|
245
|
-
url: str
|
|
246
|
-
# Optional HTTP headers to forward to the MCP transport (e.g. Authorization)
|
|
247
|
-
headers: Optional[Dict[str, str]] = None
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
class ConnectStreamableHttpMCPRequest(BaseModel):
|
|
251
|
-
sessionId: str
|
|
252
|
-
clientType: Literal["streamable-http"]
|
|
253
|
-
name: str
|
|
254
|
-
url: str
|
|
255
|
-
# Optional HTTP headers to forward to the MCP transport (e.g. Authorization)
|
|
256
|
-
headers: Dict[str, str] | None = None
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
ConnectMCPRequest = Union[
|
|
260
|
-
ConnectStdioMCPRequest, ConnectSseMCPRequest, ConnectStreamableHttpMCPRequest
|
|
261
|
-
]
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
class DisconnectMCPRequest(BaseModel):
|
|
265
|
-
sessionId: str
|
|
266
|
-
name: str
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
class ElementRequest(BaseModel):
|
|
270
|
-
element: Dict
|
|
271
|
-
sessionId: str
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
class Theme(str, Enum):
|
|
275
|
-
light = "light"
|
|
276
|
-
dark = "dark"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
@dataclass
|
|
280
|
-
class Starter(DataClassJsonMixin):
|
|
281
|
-
"""Specification for a starter that can be chosen by the user at the thread start."""
|
|
282
|
-
|
|
283
|
-
label: str
|
|
284
|
-
message: str
|
|
285
|
-
command: Optional[str] = None
|
|
286
|
-
icon: Optional[str] = None
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
@dataclass
|
|
290
|
-
class ChatProfile(DataClassJsonMixin):
|
|
291
|
-
"""Specification for a chat profile that can be chosen by the user at the thread start."""
|
|
292
|
-
|
|
293
|
-
name: str
|
|
294
|
-
markdown_description: str
|
|
295
|
-
icon: Optional[str] = None
|
|
296
|
-
default: bool = False
|
|
297
|
-
starters: Optional[List[Starter]] = None
|
|
298
|
-
config_overrides: Any = None
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
FeedbackStrategy = Literal["BINARY"]
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
class CommandDict(TypedDict):
|
|
305
|
-
# The identifier of the command, will be displayed in the UI
|
|
306
|
-
id: str
|
|
307
|
-
# The description of the command, will be displayed in the UI
|
|
308
|
-
description: str
|
|
309
|
-
# The lucide icon name
|
|
310
|
-
icon: str
|
|
311
|
-
# Display the command as a button in the composer
|
|
312
|
-
button: Optional[bool]
|
|
313
|
-
# Whether the command will be persistent unless the user toggles it
|
|
314
|
-
persistent: Optional[bool]
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
class FeedbackDict(TypedDict):
|
|
318
|
-
forId: str
|
|
319
|
-
id: Optional[str]
|
|
320
|
-
value: Literal[0, 1]
|
|
321
|
-
comment: Optional[str]
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
@dataclass
|
|
325
|
-
class Feedback:
|
|
326
|
-
forId: str
|
|
327
|
-
value: Literal[0, 1]
|
|
328
|
-
threadId: Optional[str] = None
|
|
329
|
-
id: Optional[str] = None
|
|
330
|
-
comment: Optional[str] = None
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
class UpdateFeedbackRequest(BaseModel):
|
|
334
|
-
feedback: Feedback
|
chainlit/user.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Literal, Optional, TypedDict
|
|
2
|
-
|
|
3
|
-
from dataclasses_json import DataClassJsonMixin
|
|
4
|
-
from pydantic import Field
|
|
5
|
-
from pydantic.dataclasses import dataclass
|
|
6
|
-
|
|
7
|
-
Provider = Literal[
|
|
8
|
-
"credentials",
|
|
9
|
-
"header",
|
|
10
|
-
"github",
|
|
11
|
-
"google",
|
|
12
|
-
"azure-ad",
|
|
13
|
-
"azure-ad-hybrid",
|
|
14
|
-
"okta",
|
|
15
|
-
"auth0",
|
|
16
|
-
"descope",
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class UserDict(TypedDict):
|
|
21
|
-
id: str
|
|
22
|
-
identifier: str
|
|
23
|
-
display_name: Optional[str]
|
|
24
|
-
metadata: Dict
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# Used when logging-in a user
|
|
28
|
-
@dataclass
|
|
29
|
-
class User(DataClassJsonMixin):
|
|
30
|
-
identifier: str
|
|
31
|
-
display_name: Optional[str] = None
|
|
32
|
-
metadata: Dict = Field(default_factory=dict)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@dataclass
|
|
36
|
-
class PersistedUserFields:
|
|
37
|
-
id: str
|
|
38
|
-
createdAt: str
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@dataclass
|
|
42
|
-
class PersistedUser(User, PersistedUserFields):
|
|
43
|
-
pass
|
chainlit/user_session.py
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
from typing import Callable, Dict, Generic, Optional, TypeVar
|
|
2
|
-
|
|
3
|
-
from chainlit.context import context
|
|
4
|
-
|
|
5
|
-
user_sessions: Dict[str, Dict] = {}
|
|
6
|
-
|
|
7
|
-
T = TypeVar("T")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class UserSession:
|
|
11
|
-
"""
|
|
12
|
-
Developer facing user session class.
|
|
13
|
-
Useful for the developer to store user specific data between calls.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
def get(self, key, default=None):
|
|
17
|
-
if not context.session:
|
|
18
|
-
return default
|
|
19
|
-
|
|
20
|
-
if context.session.id not in user_sessions:
|
|
21
|
-
# Create a new user session
|
|
22
|
-
user_sessions[context.session.id] = {}
|
|
23
|
-
|
|
24
|
-
user_session = user_sessions[context.session.id]
|
|
25
|
-
|
|
26
|
-
# Copy important fields from the session
|
|
27
|
-
user_session["id"] = context.session.id
|
|
28
|
-
user_session["env"] = context.session.user_env
|
|
29
|
-
user_session["chat_settings"] = context.session.chat_settings
|
|
30
|
-
user_session["user"] = context.session.user
|
|
31
|
-
user_session["chat_profile"] = context.session.chat_profile
|
|
32
|
-
user_session["client_type"] = context.session.client_type
|
|
33
|
-
|
|
34
|
-
return user_session.get(key, default)
|
|
35
|
-
|
|
36
|
-
def set(self, key, value):
|
|
37
|
-
if not context.session:
|
|
38
|
-
return None
|
|
39
|
-
|
|
40
|
-
if context.session.id not in user_sessions:
|
|
41
|
-
user_sessions[context.session.id] = {}
|
|
42
|
-
|
|
43
|
-
user_session = user_sessions[context.session.id]
|
|
44
|
-
user_session[key] = value
|
|
45
|
-
|
|
46
|
-
def create_accessor(
|
|
47
|
-
self, key: str, default: T, *, apply_fn: Optional[Callable[[T], T]] = None
|
|
48
|
-
) -> "SessionAccessor[T]":
|
|
49
|
-
"""
|
|
50
|
-
Create a typed session accessor object for the given key and default value.
|
|
51
|
-
|
|
52
|
-
#### Note: Creates the accessor configuration. The session value itself is only stored/updated when `.set()`, `.reset()`, or `.apply()` are called.
|
|
53
|
-
|
|
54
|
-
Parameters
|
|
55
|
-
----------
|
|
56
|
-
key : str
|
|
57
|
-
The session dictionary key to store the value under
|
|
58
|
-
default : T
|
|
59
|
-
Default value to return when key is not present in session
|
|
60
|
-
apply_fn : Optional[Callable[[T], T]], default None
|
|
61
|
-
Optional function to transform the value when apply() is called
|
|
62
|
-
|
|
63
|
-
Returns
|
|
64
|
-
-------
|
|
65
|
-
SessionAccessor[T]
|
|
66
|
-
A typed accessor object bound to the specified session key
|
|
67
|
-
|
|
68
|
-
Examples
|
|
69
|
-
--------
|
|
70
|
-
|
|
71
|
-
```python
|
|
72
|
-
count = cl.user_session.create_accessor("count", 0)
|
|
73
|
-
count.get() # returns 0
|
|
74
|
-
count.set(5) # type-safe setter
|
|
75
|
-
count.get() # returns 5
|
|
76
|
-
|
|
77
|
-
# With transform function
|
|
78
|
-
counter = cl.user_session.create_accessor("counter", 0, apply_fn=lambda x: x + 1)
|
|
79
|
-
counter.apply() # increments value and returns new value (1)
|
|
80
|
-
|
|
81
|
-
@cl.on_message
|
|
82
|
-
async def on_message(message: cl.Message):
|
|
83
|
-
await cl.Message(content=f"You sent {counter.apply()} messages").send() # You sent 2 messages
|
|
84
|
-
```
|
|
85
|
-
"""
|
|
86
|
-
return SessionAccessor(key, default, apply_fn=apply_fn)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
user_session = UserSession()
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class SessionAccessor(Generic[T]):
|
|
93
|
-
"""
|
|
94
|
-
Extended session accessor class to store user specific data between calls with type safety.
|
|
95
|
-
|
|
96
|
-
Provides a typed wrapper around user_session dictionary access with default values
|
|
97
|
-
and optional transform functions. The session value is only stored in memory when
|
|
98
|
-
explicitly modified through `.set()`, `.reset()`, or `.apply()` methods.
|
|
99
|
-
|
|
100
|
-
Examples
|
|
101
|
-
--------
|
|
102
|
-
```python
|
|
103
|
-
count = cl.user_session.create_accessor("count", 0)
|
|
104
|
-
count.get() # returns 0
|
|
105
|
-
count.set(5) # type-safe setter
|
|
106
|
-
count.get() # returns 5
|
|
107
|
-
|
|
108
|
-
# With transform function
|
|
109
|
-
counter = cl.user_session.create_accessor("counter", 0, apply_fn=lambda x: x + 1)
|
|
110
|
-
counter.apply() # increments value and returns new value (1)
|
|
111
|
-
|
|
112
|
-
@cl.on_message
|
|
113
|
-
async def on_message(message: cl.Message):
|
|
114
|
-
await cl.Message(content=f"You sent {counter.apply()} messages").send() # You sent 2 messages
|
|
115
|
-
```
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
def __init__(
|
|
119
|
-
self, key: str, default: T, *, apply_fn: Optional[Callable[[T], T]] = None
|
|
120
|
-
):
|
|
121
|
-
self._key = key
|
|
122
|
-
self._default = default
|
|
123
|
-
self._apply_fn = apply_fn
|
|
124
|
-
|
|
125
|
-
def get(self) -> T:
|
|
126
|
-
"""
|
|
127
|
-
Get the current value of the accessor.
|
|
128
|
-
"""
|
|
129
|
-
return user_session.get(self._key, self._default)
|
|
130
|
-
|
|
131
|
-
def set(self, value: T) -> None:
|
|
132
|
-
"""
|
|
133
|
-
Set the value of the accessor.
|
|
134
|
-
"""
|
|
135
|
-
return user_session.set(self._key, value)
|
|
136
|
-
|
|
137
|
-
def reset(self) -> None:
|
|
138
|
-
"""
|
|
139
|
-
Reset the value to the default.
|
|
140
|
-
"""
|
|
141
|
-
return self.set(self._default)
|
|
142
|
-
|
|
143
|
-
def apply(self) -> T:
|
|
144
|
-
"""
|
|
145
|
-
Apply the transform function to the current value, store the result, and return it.
|
|
146
|
-
|
|
147
|
-
Returns the current value if no transform function is provided.
|
|
148
|
-
"""
|
|
149
|
-
value = self.get()
|
|
150
|
-
if self._apply_fn:
|
|
151
|
-
value = self._apply_fn(value)
|
|
152
|
-
self.set(value)
|
|
153
|
-
return value
|
chainlit/utils.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import importlib
|
|
3
|
-
import inspect
|
|
4
|
-
import os
|
|
5
|
-
from asyncio import CancelledError
|
|
6
|
-
from datetime import datetime, timezone
|
|
7
|
-
from typing import Callable
|
|
8
|
-
|
|
9
|
-
import click
|
|
10
|
-
from fastapi import FastAPI, Request
|
|
11
|
-
from fastapi.responses import JSONResponse
|
|
12
|
-
from packaging import version
|
|
13
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
14
|
-
|
|
15
|
-
from chainlit.auth import ensure_jwt_secret
|
|
16
|
-
from chainlit.context import context
|
|
17
|
-
from chainlit.logger import logger
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def utc_now():
|
|
21
|
-
dt = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
22
|
-
return dt.isoformat() + "Z"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def timestamp_utc(timestamp: float):
|
|
26
|
-
dt = datetime.fromtimestamp(timestamp, timezone.utc).replace(tzinfo=None)
|
|
27
|
-
return dt.isoformat() + "Z"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def wrap_user_function(user_function: Callable, with_task=False) -> Callable:
|
|
31
|
-
"""
|
|
32
|
-
Wraps a user-defined function to accept arguments as a dictionary.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
user_function (Callable): The user-defined function to wrap.
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
Callable: The wrapped function.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
@functools.wraps(user_function)
|
|
42
|
-
async def wrapper(*args):
|
|
43
|
-
# Get the parameter names of the user-defined function
|
|
44
|
-
user_function_params = list(inspect.signature(user_function).parameters.keys())
|
|
45
|
-
|
|
46
|
-
# Create a dictionary of parameter names and their corresponding values from *args
|
|
47
|
-
params_values = {
|
|
48
|
-
param_name: arg for param_name, arg in zip(user_function_params, args)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if with_task:
|
|
52
|
-
await context.emitter.task_start()
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
# Call the user-defined function with the arguments
|
|
56
|
-
if inspect.iscoroutinefunction(user_function):
|
|
57
|
-
return await user_function(**params_values)
|
|
58
|
-
else:
|
|
59
|
-
return user_function(**params_values)
|
|
60
|
-
except CancelledError:
|
|
61
|
-
pass
|
|
62
|
-
except Exception as e:
|
|
63
|
-
logger.exception(e)
|
|
64
|
-
if with_task:
|
|
65
|
-
from chainlit.message import ErrorMessage
|
|
66
|
-
|
|
67
|
-
await ErrorMessage(
|
|
68
|
-
content=str(e) or e.__class__.__name__, author="Error"
|
|
69
|
-
).send()
|
|
70
|
-
finally:
|
|
71
|
-
if with_task:
|
|
72
|
-
await context.emitter.task_end()
|
|
73
|
-
|
|
74
|
-
return wrapper
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def make_module_getattr(registry):
|
|
78
|
-
"""Leverage PEP 562 to make imports lazy in an __init__.py
|
|
79
|
-
|
|
80
|
-
The registry must be a dictionary with the items to import as keys and the
|
|
81
|
-
modules they belong to as a value.
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
def __getattr__(name):
|
|
85
|
-
module_path = registry[name]
|
|
86
|
-
module = importlib.import_module(module_path, __package__)
|
|
87
|
-
return getattr(module, name)
|
|
88
|
-
|
|
89
|
-
return __getattr__
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def check_module_version(name, required_version):
|
|
93
|
-
"""
|
|
94
|
-
Check the version of a module.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
name (str): A module name.
|
|
98
|
-
version (str): Minimum version.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
(bool): Return True if the module is installed and the version
|
|
102
|
-
match the minimum required version.
|
|
103
|
-
"""
|
|
104
|
-
try:
|
|
105
|
-
module = importlib.import_module(name)
|
|
106
|
-
except ModuleNotFoundError:
|
|
107
|
-
return False
|
|
108
|
-
return version.parse(module.__version__) >= version.parse(required_version)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def check_file(target: str):
|
|
112
|
-
# Define accepted file extensions for Chainlit
|
|
113
|
-
ACCEPTED_FILE_EXTENSIONS = ("py", "py3")
|
|
114
|
-
|
|
115
|
-
_, extension = os.path.splitext(target)
|
|
116
|
-
|
|
117
|
-
# Check file extension
|
|
118
|
-
if extension[1:] not in ACCEPTED_FILE_EXTENSIONS:
|
|
119
|
-
if extension[1:] == "":
|
|
120
|
-
raise click.BadArgumentUsage(
|
|
121
|
-
"Chainlit requires raw Python (.py) files, but the provided file has no extension."
|
|
122
|
-
)
|
|
123
|
-
else:
|
|
124
|
-
raise click.BadArgumentUsage(
|
|
125
|
-
f"Chainlit requires raw Python (.py) files, not {extension}."
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
if not os.path.exists(target):
|
|
129
|
-
raise click.BadParameter(f"File does not exist: {target}")
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def mount_chainlit(app: FastAPI, target: str, path="/chainlit"):
|
|
133
|
-
from chainlit.config import config, load_module
|
|
134
|
-
from chainlit.server import app as chainlit_app
|
|
135
|
-
|
|
136
|
-
config.run.debug = os.environ.get("CHAINLIT_DEBUG", False)
|
|
137
|
-
os.environ["CHAINLIT_ROOT_PATH"] = path
|
|
138
|
-
|
|
139
|
-
api_full_path = path
|
|
140
|
-
|
|
141
|
-
if app.root_path:
|
|
142
|
-
parent_root_path = app.root_path.rstrip("/")
|
|
143
|
-
api_full_path = parent_root_path + path
|
|
144
|
-
os.environ["CHAINLIT_PARENT_ROOT_PATH"] = parent_root_path
|
|
145
|
-
|
|
146
|
-
check_file(target)
|
|
147
|
-
# Load the module provided by the user
|
|
148
|
-
config.run.module_name = target
|
|
149
|
-
load_module(config.run.module_name)
|
|
150
|
-
|
|
151
|
-
ensure_jwt_secret()
|
|
152
|
-
|
|
153
|
-
class ChainlitMiddleware(BaseHTTPMiddleware):
|
|
154
|
-
"""Middleware to handle path routing for submounted Chainlit applications.
|
|
155
|
-
|
|
156
|
-
When Chainlit is submounted within a larger FastAPI application, its default route
|
|
157
|
-
`@router.get("/{full_path:path}")` can conflict with the main app's routing. This
|
|
158
|
-
middleware ensures requests are only forwarded to Chainlit if they match the
|
|
159
|
-
designated subpath, preventing routing collisions.
|
|
160
|
-
|
|
161
|
-
If a request's path doesn't start with the configured subpath, the middleware
|
|
162
|
-
returns a 404 response instead of forwarding to Chainlit's default route.
|
|
163
|
-
"""
|
|
164
|
-
|
|
165
|
-
async def dispatch(self, request: Request, call_next):
|
|
166
|
-
if not request.url.path.startswith(api_full_path):
|
|
167
|
-
return JSONResponse(status_code=404, content={"detail": "Not found"})
|
|
168
|
-
|
|
169
|
-
return await call_next(request)
|
|
170
|
-
|
|
171
|
-
chainlit_app.add_middleware(ChainlitMiddleware)
|
|
172
|
-
|
|
173
|
-
app.mount(path, chainlit_app)
|
chainlit/version.py
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
from importlib import metadata
|
|
2
|
-
|
|
3
|
-
try:
|
|
4
|
-
__version__ = metadata.version(__package__)
|
|
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__ = "2.7.0"
|