chainlit 1.1.403rc0__py3-none-any.whl → 1.2.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.
- chainlit/__init__.py +53 -305
- chainlit/_utils.py +8 -0
- chainlit/callbacks.py +308 -0
- chainlit/config.py +60 -33
- chainlit/copilot/dist/index.js +510 -629
- chainlit/data/__init__.py +6 -512
- chainlit/data/base.py +121 -0
- chainlit/data/dynamodb.py +2 -5
- chainlit/data/literalai.py +395 -0
- chainlit/data/sql_alchemy.py +10 -9
- chainlit/data/storage_clients.py +69 -15
- chainlit/data/utils.py +29 -0
- chainlit/element.py +3 -7
- chainlit/frontend/dist/assets/{DailyMotion-dd3d0f11.js → DailyMotion-05f4fe48.js} +1 -1
- chainlit/frontend/dist/assets/{Facebook-53a09094.js → Facebook-f25411d1.js} +1 -1
- chainlit/frontend/dist/assets/{FilePlayer-7fd97f72.js → FilePlayer-40ff3414.js} +1 -1
- chainlit/frontend/dist/assets/{Kaltura-127ca0f7.js → Kaltura-6cbf3897.js} +1 -1
- chainlit/frontend/dist/assets/{Mixcloud-14459c9d.js → Mixcloud-34e7c912.js} +1 -1
- chainlit/frontend/dist/assets/{Mux-913f2511.js → Mux-8aaff6ac.js} +1 -1
- chainlit/frontend/dist/assets/{Preview-cb48c96c.js → Preview-2d3bf558.js} +1 -1
- chainlit/frontend/dist/assets/{SoundCloud-f416790b.js → SoundCloud-b835f90f.js} +1 -1
- chainlit/frontend/dist/assets/{Streamable-304738e0.js → Streamable-1293e4f3.js} +1 -1
- chainlit/frontend/dist/assets/{Twitch-0fdf7e43.js → Twitch-c69660cd.js} +1 -1
- chainlit/frontend/dist/assets/{Vidyard-0fb7aefe.js → Vidyard-43bda599.js} +1 -1
- chainlit/frontend/dist/assets/{Vimeo-10a415ff.js → Vimeo-54150039.js} +1 -1
- chainlit/frontend/dist/assets/{Wistia-7239a75e.js → Wistia-aa3c721b.js} +1 -1
- chainlit/frontend/dist/assets/{YouTube-f2b37e5e.js → YouTube-dd0f3cc2.js} +1 -1
- chainlit/frontend/dist/assets/index-cf48bedd.js +729 -0
- chainlit/frontend/dist/assets/react-plotly-f52a41eb.js +3484 -0
- chainlit/frontend/dist/index.html +1 -1
- chainlit/langchain/callbacks.py +6 -1
- chainlit/llama_index/callbacks.py +21 -5
- chainlit/markdown.py +15 -9
- chainlit/message.py +0 -1
- chainlit/server.py +90 -36
- chainlit/session.py +4 -1
- chainlit/translations/bn.json +231 -0
- chainlit/translations/gu.json +231 -0
- chainlit/translations/he-IL.json +231 -0
- chainlit/translations/hi.json +231 -0
- chainlit/translations/kn.json +231 -0
- chainlit/translations/ml.json +231 -0
- chainlit/translations/mr.json +231 -0
- chainlit/translations/ta.json +231 -0
- chainlit/translations/te.json +231 -0
- chainlit/utils.py +1 -1
- {chainlit-1.1.403rc0.dist-info → chainlit-1.2.0.dist-info}/METADATA +5 -6
- chainlit-1.2.0.dist-info/RECORD +96 -0
- chainlit/frontend/dist/assets/index-b8952cd9.js +0 -730
- chainlit/frontend/dist/assets/react-plotly-cae83415.js +0 -3602
- chainlit-1.1.403rc0.dist-info/RECORD +0 -82
- {chainlit-1.1.403rc0.dist-info → chainlit-1.2.0.dist-info}/WHEEL +0 -0
- {chainlit-1.1.403rc0.dist-info → chainlit-1.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Union, cast
|
|
3
|
+
|
|
4
|
+
import aiofiles
|
|
5
|
+
from chainlit.data.base import BaseDataLayer
|
|
6
|
+
from chainlit.data.utils import queue_until_user_message
|
|
7
|
+
from chainlit.logger import logger
|
|
8
|
+
from chainlit.types import (
|
|
9
|
+
Feedback,
|
|
10
|
+
PageInfo,
|
|
11
|
+
PaginatedResponse,
|
|
12
|
+
Pagination,
|
|
13
|
+
ThreadDict,
|
|
14
|
+
ThreadFilter,
|
|
15
|
+
)
|
|
16
|
+
from chainlit.user import PersistedUser, User
|
|
17
|
+
from httpx import HTTPStatusError, RequestError
|
|
18
|
+
from literalai import Attachment
|
|
19
|
+
from literalai import Score as LiteralScore
|
|
20
|
+
from literalai import Step as LiteralStep
|
|
21
|
+
from literalai.filter import threads_filters as LiteralThreadsFilters
|
|
22
|
+
from literalai.step import StepDict as LiteralStepDict
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from chainlit.element import Element, ElementDict
|
|
26
|
+
from chainlit.step import FeedbackDict, StepDict
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_data_layer: Optional[BaseDataLayer] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LiteralDataLayer(BaseDataLayer):
|
|
33
|
+
def __init__(self, api_key: str, server: Optional[str]):
|
|
34
|
+
from literalai import AsyncLiteralClient
|
|
35
|
+
|
|
36
|
+
self.client = AsyncLiteralClient(api_key=api_key, url=server)
|
|
37
|
+
logger.info("Chainlit data layer initialized")
|
|
38
|
+
|
|
39
|
+
def attachment_to_element_dict(self, attachment: Attachment) -> "ElementDict":
|
|
40
|
+
metadata = attachment.metadata or {}
|
|
41
|
+
return {
|
|
42
|
+
"chainlitKey": None,
|
|
43
|
+
"display": metadata.get("display", "side"),
|
|
44
|
+
"language": metadata.get("language"),
|
|
45
|
+
"autoPlay": metadata.get("autoPlay", None),
|
|
46
|
+
"playerConfig": metadata.get("playerConfig", None),
|
|
47
|
+
"page": metadata.get("page"),
|
|
48
|
+
"size": metadata.get("size"),
|
|
49
|
+
"type": metadata.get("type", "file"),
|
|
50
|
+
"forId": attachment.step_id,
|
|
51
|
+
"id": attachment.id or "",
|
|
52
|
+
"mime": attachment.mime,
|
|
53
|
+
"name": attachment.name or "",
|
|
54
|
+
"objectKey": attachment.object_key,
|
|
55
|
+
"url": attachment.url,
|
|
56
|
+
"threadId": attachment.thread_id,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def score_to_feedback_dict(
|
|
60
|
+
self, score: Optional[LiteralScore]
|
|
61
|
+
) -> "Optional[FeedbackDict]":
|
|
62
|
+
if not score:
|
|
63
|
+
return None
|
|
64
|
+
return {
|
|
65
|
+
"id": score.id or "",
|
|
66
|
+
"forId": score.step_id or "",
|
|
67
|
+
"value": cast(Literal[0, 1], score.value),
|
|
68
|
+
"comment": score.comment,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def step_to_step_dict(self, step: LiteralStep) -> "StepDict":
|
|
72
|
+
metadata = step.metadata or {}
|
|
73
|
+
input = (step.input or {}).get("content") or (
|
|
74
|
+
json.dumps(step.input) if step.input and step.input != {} else ""
|
|
75
|
+
)
|
|
76
|
+
output = (step.output or {}).get("content") or (
|
|
77
|
+
json.dumps(step.output) if step.output and step.output != {} else ""
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
user_feedback = (
|
|
81
|
+
next(
|
|
82
|
+
(
|
|
83
|
+
s
|
|
84
|
+
for s in step.scores
|
|
85
|
+
if s.type == "HUMAN" and s.name == "user-feedback"
|
|
86
|
+
),
|
|
87
|
+
None,
|
|
88
|
+
)
|
|
89
|
+
if step.scores
|
|
90
|
+
else None
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"createdAt": step.created_at,
|
|
95
|
+
"id": step.id or "",
|
|
96
|
+
"threadId": step.thread_id or "",
|
|
97
|
+
"parentId": step.parent_id,
|
|
98
|
+
"feedback": self.score_to_feedback_dict(user_feedback),
|
|
99
|
+
"start": step.start_time,
|
|
100
|
+
"end": step.end_time,
|
|
101
|
+
"type": step.type or "undefined",
|
|
102
|
+
"name": step.name or "",
|
|
103
|
+
"generation": step.generation.to_dict() if step.generation else None,
|
|
104
|
+
"input": input,
|
|
105
|
+
"output": output,
|
|
106
|
+
"showInput": metadata.get("showInput", False),
|
|
107
|
+
"indent": metadata.get("indent"),
|
|
108
|
+
"language": metadata.get("language"),
|
|
109
|
+
"isError": bool(step.error),
|
|
110
|
+
"waitForAnswer": metadata.get("waitForAnswer", False),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async def build_debug_url(self) -> str:
|
|
114
|
+
try:
|
|
115
|
+
project_id = await self.client.api.get_my_project_id()
|
|
116
|
+
return f"{self.client.api.url}/projects/{project_id}/logs/threads/[thread_id]?currentStepId=[step_id]"
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.error(f"Error building debug url: {e}")
|
|
119
|
+
return ""
|
|
120
|
+
|
|
121
|
+
async def get_user(self, identifier: str) -> Optional[PersistedUser]:
|
|
122
|
+
user = await self.client.api.get_user(identifier=identifier)
|
|
123
|
+
if not user:
|
|
124
|
+
return None
|
|
125
|
+
return PersistedUser(
|
|
126
|
+
id=user.id or "",
|
|
127
|
+
identifier=user.identifier or "",
|
|
128
|
+
metadata=user.metadata,
|
|
129
|
+
createdAt=user.created_at or "",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
async def create_user(self, user: User) -> Optional[PersistedUser]:
|
|
133
|
+
_user = await self.client.api.get_user(identifier=user.identifier)
|
|
134
|
+
if not _user:
|
|
135
|
+
_user = await self.client.api.create_user(
|
|
136
|
+
identifier=user.identifier, metadata=user.metadata
|
|
137
|
+
)
|
|
138
|
+
elif _user.id:
|
|
139
|
+
await self.client.api.update_user(id=_user.id, metadata=user.metadata)
|
|
140
|
+
return PersistedUser(
|
|
141
|
+
id=_user.id or "",
|
|
142
|
+
identifier=_user.identifier or "",
|
|
143
|
+
metadata=user.metadata,
|
|
144
|
+
createdAt=_user.created_at or "",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
async def delete_feedback(
|
|
148
|
+
self,
|
|
149
|
+
feedback_id: str,
|
|
150
|
+
):
|
|
151
|
+
if feedback_id:
|
|
152
|
+
await self.client.api.delete_score(
|
|
153
|
+
id=feedback_id,
|
|
154
|
+
)
|
|
155
|
+
return True
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
async def upsert_feedback(
|
|
159
|
+
self,
|
|
160
|
+
feedback: Feedback,
|
|
161
|
+
):
|
|
162
|
+
if feedback.id:
|
|
163
|
+
await self.client.api.update_score(
|
|
164
|
+
id=feedback.id,
|
|
165
|
+
update_params={
|
|
166
|
+
"comment": feedback.comment,
|
|
167
|
+
"value": feedback.value,
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
return feedback.id
|
|
171
|
+
else:
|
|
172
|
+
created = await self.client.api.create_score(
|
|
173
|
+
step_id=feedback.forId,
|
|
174
|
+
value=feedback.value,
|
|
175
|
+
comment=feedback.comment,
|
|
176
|
+
name="user-feedback",
|
|
177
|
+
type="HUMAN",
|
|
178
|
+
)
|
|
179
|
+
return created.id or ""
|
|
180
|
+
|
|
181
|
+
async def safely_send_steps(self, steps):
|
|
182
|
+
try:
|
|
183
|
+
await self.client.api.send_steps(steps)
|
|
184
|
+
except HTTPStatusError as e:
|
|
185
|
+
logger.error(f"HTTP Request: error sending steps: {e.response.status_code}")
|
|
186
|
+
except RequestError as e:
|
|
187
|
+
logger.error(f"HTTP Request: error for {e.request.url!r}.")
|
|
188
|
+
|
|
189
|
+
@queue_until_user_message()
|
|
190
|
+
async def create_element(self, element: "Element"):
|
|
191
|
+
metadata = {
|
|
192
|
+
"size": element.size,
|
|
193
|
+
"language": element.language,
|
|
194
|
+
"display": element.display,
|
|
195
|
+
"type": element.type,
|
|
196
|
+
"page": getattr(element, "page", None),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if not element.for_id:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
object_key = None
|
|
203
|
+
|
|
204
|
+
if not element.url:
|
|
205
|
+
if element.path:
|
|
206
|
+
async with aiofiles.open(element.path, "rb") as f:
|
|
207
|
+
content: Union[bytes, str] = await f.read()
|
|
208
|
+
elif element.content:
|
|
209
|
+
content = element.content
|
|
210
|
+
else:
|
|
211
|
+
raise ValueError("Either path or content must be provided")
|
|
212
|
+
uploaded = await self.client.api.upload_file(
|
|
213
|
+
content=content, mime=element.mime, thread_id=element.thread_id
|
|
214
|
+
)
|
|
215
|
+
object_key = uploaded["object_key"]
|
|
216
|
+
|
|
217
|
+
await self.safely_send_steps(
|
|
218
|
+
[
|
|
219
|
+
{
|
|
220
|
+
"id": element.for_id,
|
|
221
|
+
"threadId": element.thread_id,
|
|
222
|
+
"attachments": [
|
|
223
|
+
{
|
|
224
|
+
"id": element.id,
|
|
225
|
+
"name": element.name,
|
|
226
|
+
"metadata": metadata,
|
|
227
|
+
"mime": element.mime,
|
|
228
|
+
"url": element.url,
|
|
229
|
+
"objectKey": object_key,
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
async def get_element(
|
|
237
|
+
self, thread_id: str, element_id: str
|
|
238
|
+
) -> Optional["ElementDict"]:
|
|
239
|
+
attachment = await self.client.api.get_attachment(id=element_id)
|
|
240
|
+
if not attachment:
|
|
241
|
+
return None
|
|
242
|
+
return self.attachment_to_element_dict(attachment)
|
|
243
|
+
|
|
244
|
+
@queue_until_user_message()
|
|
245
|
+
async def delete_element(self, element_id: str, thread_id: Optional[str] = None):
|
|
246
|
+
await self.client.api.delete_attachment(id=element_id)
|
|
247
|
+
|
|
248
|
+
@queue_until_user_message()
|
|
249
|
+
async def create_step(self, step_dict: "StepDict"):
|
|
250
|
+
metadata = dict(
|
|
251
|
+
step_dict.get("metadata", {}),
|
|
252
|
+
**{
|
|
253
|
+
"waitForAnswer": step_dict.get("waitForAnswer"),
|
|
254
|
+
"language": step_dict.get("language"),
|
|
255
|
+
"showInput": step_dict.get("showInput"),
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
step: LiteralStepDict = {
|
|
260
|
+
"createdAt": step_dict.get("createdAt"),
|
|
261
|
+
"startTime": step_dict.get("start"),
|
|
262
|
+
"endTime": step_dict.get("end"),
|
|
263
|
+
"generation": step_dict.get("generation"),
|
|
264
|
+
"id": step_dict.get("id"),
|
|
265
|
+
"parentId": step_dict.get("parentId"),
|
|
266
|
+
"name": step_dict.get("name"),
|
|
267
|
+
"threadId": step_dict.get("threadId"),
|
|
268
|
+
"type": step_dict.get("type"),
|
|
269
|
+
"tags": step_dict.get("tags"),
|
|
270
|
+
"metadata": metadata,
|
|
271
|
+
}
|
|
272
|
+
if step_dict.get("input"):
|
|
273
|
+
step["input"] = {"content": step_dict.get("input")}
|
|
274
|
+
if step_dict.get("output"):
|
|
275
|
+
step["output"] = {"content": step_dict.get("output")}
|
|
276
|
+
if step_dict.get("isError"):
|
|
277
|
+
step["error"] = step_dict.get("output")
|
|
278
|
+
|
|
279
|
+
await self.safely_send_steps([step])
|
|
280
|
+
|
|
281
|
+
@queue_until_user_message()
|
|
282
|
+
async def update_step(self, step_dict: "StepDict"):
|
|
283
|
+
await self.create_step(step_dict)
|
|
284
|
+
|
|
285
|
+
@queue_until_user_message()
|
|
286
|
+
async def delete_step(self, step_id: str):
|
|
287
|
+
await self.client.api.delete_step(id=step_id)
|
|
288
|
+
|
|
289
|
+
async def get_thread_author(self, thread_id: str) -> str:
|
|
290
|
+
thread = await self.get_thread(thread_id)
|
|
291
|
+
if not thread:
|
|
292
|
+
return ""
|
|
293
|
+
user_identifier = thread.get("userIdentifier")
|
|
294
|
+
if not user_identifier:
|
|
295
|
+
return ""
|
|
296
|
+
|
|
297
|
+
return user_identifier
|
|
298
|
+
|
|
299
|
+
async def delete_thread(self, thread_id: str):
|
|
300
|
+
await self.client.api.delete_thread(id=thread_id)
|
|
301
|
+
|
|
302
|
+
async def list_threads(
|
|
303
|
+
self, pagination: "Pagination", filters: "ThreadFilter"
|
|
304
|
+
) -> "PaginatedResponse[ThreadDict]":
|
|
305
|
+
if not filters.userId:
|
|
306
|
+
raise ValueError("userId is required")
|
|
307
|
+
|
|
308
|
+
literal_filters: LiteralThreadsFilters = [
|
|
309
|
+
{
|
|
310
|
+
"field": "participantId",
|
|
311
|
+
"operator": "eq",
|
|
312
|
+
"value": filters.userId,
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
if filters.search:
|
|
317
|
+
literal_filters.append(
|
|
318
|
+
{
|
|
319
|
+
"field": "stepOutput",
|
|
320
|
+
"operator": "ilike",
|
|
321
|
+
"value": filters.search,
|
|
322
|
+
"path": "content",
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if filters.feedback is not None:
|
|
327
|
+
literal_filters.append(
|
|
328
|
+
{
|
|
329
|
+
"field": "scoreValue",
|
|
330
|
+
"operator": "eq",
|
|
331
|
+
"value": filters.feedback,
|
|
332
|
+
"path": "user-feedback",
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
literal_response = await self.client.api.list_threads(
|
|
337
|
+
first=pagination.first,
|
|
338
|
+
after=pagination.cursor,
|
|
339
|
+
filters=literal_filters,
|
|
340
|
+
order_by={"column": "createdAt", "direction": "DESC"},
|
|
341
|
+
)
|
|
342
|
+
return PaginatedResponse(
|
|
343
|
+
pageInfo=PageInfo(
|
|
344
|
+
hasNextPage=literal_response.pageInfo.hasNextPage,
|
|
345
|
+
startCursor=literal_response.pageInfo.startCursor,
|
|
346
|
+
endCursor=literal_response.pageInfo.endCursor,
|
|
347
|
+
),
|
|
348
|
+
data=literal_response.data,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
|
|
352
|
+
from chainlit.step import check_add_step_in_cot, stub_step
|
|
353
|
+
|
|
354
|
+
thread = await self.client.api.get_thread(id=thread_id)
|
|
355
|
+
if not thread:
|
|
356
|
+
return None
|
|
357
|
+
elements = [] # List[ElementDict]
|
|
358
|
+
steps = [] # List[StepDict]
|
|
359
|
+
if thread.steps:
|
|
360
|
+
for step in thread.steps:
|
|
361
|
+
for attachment in step.attachments:
|
|
362
|
+
elements.append(self.attachment_to_element_dict(attachment))
|
|
363
|
+
|
|
364
|
+
if check_add_step_in_cot(step):
|
|
365
|
+
steps.append(self.step_to_step_dict(step))
|
|
366
|
+
else:
|
|
367
|
+
steps.append(stub_step(step))
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
"createdAt": thread.created_at or "",
|
|
371
|
+
"id": thread.id,
|
|
372
|
+
"name": thread.name or None,
|
|
373
|
+
"steps": steps,
|
|
374
|
+
"elements": elements,
|
|
375
|
+
"metadata": thread.metadata,
|
|
376
|
+
"userId": thread.participant_id,
|
|
377
|
+
"userIdentifier": thread.participant_identifier,
|
|
378
|
+
"tags": thread.tags,
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async def update_thread(
|
|
382
|
+
self,
|
|
383
|
+
thread_id: str,
|
|
384
|
+
name: Optional[str] = None,
|
|
385
|
+
user_id: Optional[str] = None,
|
|
386
|
+
metadata: Optional[Dict] = None,
|
|
387
|
+
tags: Optional[List[str]] = None,
|
|
388
|
+
):
|
|
389
|
+
await self.client.api.upsert_thread(
|
|
390
|
+
id=thread_id,
|
|
391
|
+
name=name,
|
|
392
|
+
participant_id=user_id,
|
|
393
|
+
metadata=metadata,
|
|
394
|
+
tags=tags,
|
|
395
|
+
)
|
chainlit/data/sql_alchemy.py
CHANGED
|
@@ -8,7 +8,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
|
8
8
|
import aiofiles
|
|
9
9
|
import aiohttp
|
|
10
10
|
from chainlit.context import context
|
|
11
|
-
from chainlit.data import BaseDataLayer, BaseStorageClient
|
|
11
|
+
from chainlit.data.base import BaseDataLayer, BaseStorageClient
|
|
12
|
+
from chainlit.data.utils import queue_until_user_message
|
|
12
13
|
from chainlit.element import ElementDict
|
|
13
14
|
from chainlit.logger import logger
|
|
14
15
|
from chainlit.step import StepDict
|
|
@@ -54,7 +55,9 @@ class SQLAlchemyDataLayer(BaseDataLayer):
|
|
|
54
55
|
self.engine: AsyncEngine = create_async_engine(
|
|
55
56
|
self._conninfo, connect_args=ssl_args
|
|
56
57
|
)
|
|
57
|
-
self.async_session = sessionmaker(
|
|
58
|
+
self.async_session = sessionmaker(
|
|
59
|
+
bind=self.engine, expire_on_commit=False, class_=AsyncSession
|
|
60
|
+
) # type: ignore
|
|
58
61
|
if storage_provider:
|
|
59
62
|
self.storage_provider: Optional[BaseStorageClient] = storage_provider
|
|
60
63
|
if self.show_logger:
|
|
@@ -378,7 +381,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
|
|
|
378
381
|
raise ValueError("No authenticated user in context")
|
|
379
382
|
if not self.storage_provider:
|
|
380
383
|
logger.warn(
|
|
381
|
-
|
|
384
|
+
"SQLAlchemy: create_element error. No blob_storage_client is configured!"
|
|
382
385
|
)
|
|
383
386
|
return
|
|
384
387
|
if not element.for_id:
|
|
@@ -440,15 +443,12 @@ class SQLAlchemyDataLayer(BaseDataLayer):
|
|
|
440
443
|
parameters = {"id": element_id}
|
|
441
444
|
await self.execute_sql(query=query, parameters=parameters)
|
|
442
445
|
|
|
443
|
-
async def delete_user_session(self, id: str) -> bool:
|
|
444
|
-
return False # Not sure why documentation wants this
|
|
445
|
-
|
|
446
446
|
async def get_all_user_threads(
|
|
447
447
|
self, user_id: Optional[str] = None, thread_id: Optional[str] = None
|
|
448
448
|
) -> Optional[List[ThreadDict]]:
|
|
449
449
|
"""Fetch all user threads up to self.user_thread_limit, or one thread by id if thread_id is provided."""
|
|
450
450
|
if self.show_logger:
|
|
451
|
-
logger.info(
|
|
451
|
+
logger.info("SQLAlchemy: get_all_user_threads")
|
|
452
452
|
user_threads_query = """
|
|
453
453
|
SELECT
|
|
454
454
|
"id" AS thread_id,
|
|
@@ -504,7 +504,8 @@ class SQLAlchemyDataLayer(BaseDataLayer):
|
|
|
504
504
|
s."language" AS step_language,
|
|
505
505
|
s."indent" AS step_indent,
|
|
506
506
|
f."value" AS feedback_value,
|
|
507
|
-
f."comment" AS feedback_comment
|
|
507
|
+
f."comment" AS feedback_comment,
|
|
508
|
+
f."id" AS feedback_id
|
|
508
509
|
FROM steps s LEFT JOIN feedbacks f ON s."id" = f."forId"
|
|
509
510
|
WHERE s."threadId" IN {thread_ids}
|
|
510
511
|
ORDER BY s."createdAt" ASC
|
|
@@ -578,7 +579,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
|
|
|
578
579
|
tags=step_feedback.get("step_tags"),
|
|
579
580
|
input=(
|
|
580
581
|
step_feedback.get("step_input", "")
|
|
581
|
-
if step_feedback
|
|
582
|
+
if step_feedback.get("step_showinput") not in [None, "false"]
|
|
582
583
|
else None
|
|
583
584
|
),
|
|
584
585
|
output=step_feedback.get("step_output", ""),
|
chainlit/data/storage_clients.py
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
|
|
2
|
+
|
|
3
|
+
import boto3 # type: ignore
|
|
4
|
+
from azure.storage.filedatalake import (
|
|
5
|
+
ContentSettings,
|
|
6
|
+
DataLakeFileClient,
|
|
7
|
+
DataLakeServiceClient,
|
|
8
|
+
FileSystemClient,
|
|
9
|
+
)
|
|
10
|
+
from chainlit.data.base import BaseStorageClient
|
|
2
11
|
from chainlit.logger import logger
|
|
3
|
-
from typing import TYPE_CHECKING, Optional, Dict, Union, Any
|
|
4
|
-
from azure.storage.filedatalake import DataLakeServiceClient, FileSystemClient, DataLakeFileClient, ContentSettings
|
|
5
|
-
import boto3 # type: ignore
|
|
6
12
|
|
|
7
13
|
if TYPE_CHECKING:
|
|
8
|
-
from azure.core.credentials import
|
|
14
|
+
from azure.core.credentials import (
|
|
15
|
+
AzureNamedKeyCredential,
|
|
16
|
+
AzureSasCredential,
|
|
17
|
+
TokenCredential,
|
|
18
|
+
)
|
|
19
|
+
|
|
9
20
|
|
|
10
21
|
class AzureStorageClient(BaseStorageClient):
|
|
11
22
|
"""
|
|
@@ -16,30 +27,65 @@ class AzureStorageClient(BaseStorageClient):
|
|
|
16
27
|
credential: Access credential (AzureKeyCredential)
|
|
17
28
|
sas_token: Optionally include SAS token to append to urls
|
|
18
29
|
"""
|
|
19
|
-
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
account_url: str,
|
|
34
|
+
container: str,
|
|
35
|
+
credential: Optional[
|
|
36
|
+
Union[
|
|
37
|
+
str,
|
|
38
|
+
Dict[str, str],
|
|
39
|
+
"AzureNamedKeyCredential",
|
|
40
|
+
"AzureSasCredential",
|
|
41
|
+
"TokenCredential",
|
|
42
|
+
]
|
|
43
|
+
],
|
|
44
|
+
sas_token: Optional[str] = None,
|
|
45
|
+
):
|
|
20
46
|
try:
|
|
21
|
-
self.data_lake_client = DataLakeServiceClient(
|
|
22
|
-
|
|
47
|
+
self.data_lake_client = DataLakeServiceClient(
|
|
48
|
+
account_url=account_url, credential=credential
|
|
49
|
+
)
|
|
50
|
+
self.container_client: FileSystemClient = (
|
|
51
|
+
self.data_lake_client.get_file_system_client(file_system=container)
|
|
52
|
+
)
|
|
23
53
|
self.sas_token = sas_token
|
|
24
54
|
logger.info("AzureStorageClient initialized")
|
|
25
55
|
except Exception as e:
|
|
26
56
|
logger.warn(f"AzureStorageClient initialization error: {e}")
|
|
27
|
-
|
|
28
|
-
async def upload_file(
|
|
57
|
+
|
|
58
|
+
async def upload_file(
|
|
59
|
+
self,
|
|
60
|
+
object_key: str,
|
|
61
|
+
data: Union[bytes, str],
|
|
62
|
+
mime: str = "application/octet-stream",
|
|
63
|
+
overwrite: bool = True,
|
|
64
|
+
) -> Dict[str, Any]:
|
|
29
65
|
try:
|
|
30
|
-
file_client: DataLakeFileClient = self.container_client.get_file_client(
|
|
66
|
+
file_client: DataLakeFileClient = self.container_client.get_file_client(
|
|
67
|
+
object_key
|
|
68
|
+
)
|
|
31
69
|
content_settings = ContentSettings(content_type=mime)
|
|
32
|
-
file_client.upload_data(
|
|
33
|
-
|
|
70
|
+
file_client.upload_data(
|
|
71
|
+
data, overwrite=overwrite, content_settings=content_settings
|
|
72
|
+
)
|
|
73
|
+
url = (
|
|
74
|
+
f"{file_client.url}{self.sas_token}"
|
|
75
|
+
if self.sas_token
|
|
76
|
+
else file_client.url
|
|
77
|
+
)
|
|
34
78
|
return {"object_key": object_key, "url": url}
|
|
35
79
|
except Exception as e:
|
|
36
80
|
logger.warn(f"AzureStorageClient, upload_file error: {e}")
|
|
37
81
|
return {}
|
|
38
82
|
|
|
83
|
+
|
|
39
84
|
class S3StorageClient(BaseStorageClient):
|
|
40
85
|
"""
|
|
41
86
|
Class to enable Amazon S3 storage provider
|
|
42
87
|
"""
|
|
88
|
+
|
|
43
89
|
def __init__(self, bucket: str):
|
|
44
90
|
try:
|
|
45
91
|
self.bucket = bucket
|
|
@@ -48,9 +94,17 @@ class S3StorageClient(BaseStorageClient):
|
|
|
48
94
|
except Exception as e:
|
|
49
95
|
logger.warn(f"S3StorageClient initialization error: {e}")
|
|
50
96
|
|
|
51
|
-
async def upload_file(
|
|
97
|
+
async def upload_file(
|
|
98
|
+
self,
|
|
99
|
+
object_key: str,
|
|
100
|
+
data: Union[bytes, str],
|
|
101
|
+
mime: str = "application/octet-stream",
|
|
102
|
+
overwrite: bool = True,
|
|
103
|
+
) -> Dict[str, Any]:
|
|
52
104
|
try:
|
|
53
|
-
self.client.put_object(
|
|
105
|
+
self.client.put_object(
|
|
106
|
+
Bucket=self.bucket, Key=object_key, Body=data, ContentType=mime
|
|
107
|
+
)
|
|
54
108
|
url = f"https://{self.bucket}.s3.amazonaws.com/{object_key}"
|
|
55
109
|
return {"object_key": object_key, "url": url}
|
|
56
110
|
except Exception as e:
|
chainlit/data/utils.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from collections import deque
|
|
3
|
+
|
|
4
|
+
from chainlit.context import context
|
|
5
|
+
from chainlit.session import WebsocketSession
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def queue_until_user_message():
|
|
9
|
+
def decorator(method):
|
|
10
|
+
@functools.wraps(method)
|
|
11
|
+
async def wrapper(self, *args, **kwargs):
|
|
12
|
+
if (
|
|
13
|
+
isinstance(context.session, WebsocketSession)
|
|
14
|
+
and not context.session.has_first_interaction
|
|
15
|
+
):
|
|
16
|
+
# Queue the method invocation waiting for the first user message
|
|
17
|
+
queues = context.session.thread_queues
|
|
18
|
+
method_name = method.__name__
|
|
19
|
+
if method_name not in queues:
|
|
20
|
+
queues[method_name] = deque()
|
|
21
|
+
queues[method_name].append((method, self, args, kwargs))
|
|
22
|
+
|
|
23
|
+
else:
|
|
24
|
+
# Otherwise, Execute the method immediately
|
|
25
|
+
return await method(self, *args, **kwargs)
|
|
26
|
+
|
|
27
|
+
return wrapper
|
|
28
|
+
|
|
29
|
+
return decorator
|
chainlit/element.py
CHANGED
|
@@ -235,14 +235,10 @@ class Pyplot(Element):
|
|
|
235
235
|
if not isinstance(self.figure, Figure):
|
|
236
236
|
raise TypeError("figure must be a matplotlib.figure.Figure")
|
|
237
237
|
|
|
238
|
-
options = {
|
|
239
|
-
"dpi": 200,
|
|
240
|
-
"bbox_inches": "tight",
|
|
241
|
-
"backend": "Agg",
|
|
242
|
-
"format": "png",
|
|
243
|
-
}
|
|
244
238
|
image = BytesIO()
|
|
245
|
-
self.figure.savefig(
|
|
239
|
+
self.figure.savefig(
|
|
240
|
+
image, dpi=200, bbox_inches="tight", backend="Agg", format="png"
|
|
241
|
+
)
|
|
246
242
|
self.content = image.getvalue()
|
|
247
243
|
|
|
248
244
|
super().__post_init__()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as P,r as v,u as D,a as O}from"./index-
|
|
1
|
+
import{g as P,r as v,u as D,a as O}from"./index-cf48bedd.js";function b(t,e){for(var r=0;r<e.length;r++){const o=e[r];if(typeof o!="string"&&!Array.isArray(o)){for(const a in o)if(a!=="default"&&!(a in t)){const i=Object.getOwnPropertyDescriptor(o,a);i&&Object.defineProperty(t,a,i.get?i:{enumerable:!0,get:()=>o[a]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var M=Object.create,s=Object.defineProperty,w=Object.getOwnPropertyDescriptor,S=Object.getOwnPropertyNames,j=Object.getPrototypeOf,T=Object.prototype.hasOwnProperty,E=(t,e,r)=>e in t?s(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,A=(t,e)=>{for(var r in e)s(t,r,{get:e[r],enumerable:!0})},h=(t,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of S(e))!T.call(t,a)&&a!==r&&s(t,a,{get:()=>e[a],enumerable:!(o=w(e,a))||o.enumerable});return t},L=(t,e,r)=>(r=t!=null?M(j(t)):{},h(e||!t||!t.__esModule?s(r,"default",{value:t,enumerable:!0}):r,t)),C=t=>h(s({},"__esModule",{value:!0}),t),n=(t,e,r)=>(E(t,typeof e!="symbol"?e+"":e,r),r),d={};A(d,{default:()=>p});var _=C(d),c=L(v),l=D,f=O;const x="https://api.dmcdn.net/all.js",N="DM",K="dmAsyncInit";class p extends c.Component{constructor(){super(...arguments),n(this,"callPlayer",l.callPlayer),n(this,"onDurationChange",()=>{const e=this.getDuration();this.props.onDuration(e)}),n(this,"mute",()=>{this.callPlayer("setMuted",!0)}),n(this,"unmute",()=>{this.callPlayer("setMuted",!1)}),n(this,"ref",e=>{this.container=e})}componentDidMount(){this.props.onMount&&this.props.onMount(this)}load(e){const{controls:r,config:o,onError:a,playing:i}=this.props,[,y]=e.match(f.MATCH_URL_DAILYMOTION);if(this.player){this.player.load(y,{start:(0,l.parseStartTime)(e),autoplay:i});return}(0,l.getSDK)(x,N,K,u=>u.player).then(u=>{if(!this.container)return;const g=u.player;this.player=new g(this.container,{width:"100%",height:"100%",video:y,params:{controls:r,autoplay:this.props.playing,mute:this.props.muted,start:(0,l.parseStartTime)(e),origin:window.location.origin,...o.params},events:{apiready:this.props.onReady,seeked:()=>this.props.onSeek(this.player.currentTime),video_end:this.props.onEnded,durationchange:this.onDurationChange,pause:this.props.onPause,playing:this.props.onPlay,waiting:this.props.onBuffer,error:m=>a(m)}})},a)}play(){this.callPlayer("play")}pause(){this.callPlayer("pause")}stop(){}seekTo(e,r=!0){this.callPlayer("seek",e),r||this.pause()}setVolume(e){this.callPlayer("setVolume",e)}getDuration(){return this.player.duration||null}getCurrentTime(){return this.player.currentTime}getSecondsLoaded(){return this.player.bufferedTime}render(){const{display:e}=this.props,r={width:"100%",height:"100%",display:e};return c.default.createElement("div",{style:r},c.default.createElement("div",{ref:this.ref}))}}n(p,"displayName","DailyMotion");n(p,"canPlay",f.canPlay.dailymotion);n(p,"loopOnEnded",!0);const R=P(_),I=b({__proto__:null,default:R},[_]);export{I as D};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as _,r as g,u as P,a as m}from"./index-
|
|
1
|
+
import{g as _,r as g,u as P,a as m}from"./index-cf48bedd.js";function v(t,e){for(var r=0;r<e.length;r++){const a=e[r];if(typeof a!="string"&&!Array.isArray(a)){for(const s in a)if(s!=="default"&&!(s in t)){const p=Object.getOwnPropertyDescriptor(a,s);p&&Object.defineProperty(t,s,p.get?p:{enumerable:!0,get:()=>a[s]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var O=Object.create,i=Object.defineProperty,D=Object.getOwnPropertyDescriptor,E=Object.getOwnPropertyNames,S=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty,I=(t,e,r)=>e in t?i(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,k=(t,e)=>{for(var r in e)i(t,r,{get:e[r],enumerable:!0})},h=(t,e,r,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of E(e))!j.call(t,s)&&s!==r&&i(t,s,{get:()=>e[s],enumerable:!(a=D(e,s))||a.enumerable});return t},w=(t,e,r)=>(r=t!=null?O(S(t)):{},h(e||!t||!t.__esModule?i(r,"default",{value:t,enumerable:!0}):r,t)),F=t=>h(i({},"__esModule",{value:!0}),t),o=(t,e,r)=>(I(t,typeof e!="symbol"?e+"":e,r),r),b={};k(b,{default:()=>l});var d=F(b),u=w(g),n=P,x=m;const c="https://connect.facebook.net/en_US/sdk.js",y="FB",f="fbAsyncInit",L="facebook-player-";class l extends u.Component{constructor(){super(...arguments),o(this,"callPlayer",n.callPlayer),o(this,"playerID",this.props.config.playerId||`${L}${(0,n.randomString)()}`),o(this,"mute",()=>{this.callPlayer("mute")}),o(this,"unmute",()=>{this.callPlayer("unmute")})}componentDidMount(){this.props.onMount&&this.props.onMount(this)}load(e,r){if(r){(0,n.getSDK)(c,y,f).then(a=>a.XFBML.parse());return}(0,n.getSDK)(c,y,f).then(a=>{a.init({appId:this.props.config.appId,xfbml:!0,version:this.props.config.version}),a.Event.subscribe("xfbml.render",s=>{this.props.onLoaded()}),a.Event.subscribe("xfbml.ready",s=>{s.type==="video"&&s.id===this.playerID&&(this.player=s.instance,this.player.subscribe("startedPlaying",this.props.onPlay),this.player.subscribe("paused",this.props.onPause),this.player.subscribe("finishedPlaying",this.props.onEnded),this.player.subscribe("startedBuffering",this.props.onBuffer),this.player.subscribe("finishedBuffering",this.props.onBufferEnd),this.player.subscribe("error",this.props.onError),this.props.muted?this.callPlayer("mute"):this.callPlayer("unmute"),this.props.onReady(),document.getElementById(this.playerID).querySelector("iframe").style.visibility="visible")})})}play(){this.callPlayer("play")}pause(){this.callPlayer("pause")}stop(){}seekTo(e,r=!0){this.callPlayer("seek",e),r||this.pause()}setVolume(e){this.callPlayer("setVolume",e)}getDuration(){return this.callPlayer("getDuration")}getCurrentTime(){return this.callPlayer("getCurrentPosition")}getSecondsLoaded(){return null}render(){const{attributes:e}=this.props.config,r={width:"100%",height:"100%"};return u.default.createElement("div",{style:r,id:this.playerID,className:"fb-video","data-href":this.props.url,"data-autoplay":this.props.playing?"true":"false","data-allowfullscreen":"true","data-controls":this.props.controls?"true":"false",...e})}}o(l,"displayName","Facebook");o(l,"canPlay",x.canPlay.facebook);o(l,"loopOnEnded",!0);const M=_(d),B=v({__proto__:null,default:M},[d]);export{B as F};
|