chainlit 1.3.2__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-Bq4wFES6.js → DailyMotion-DgRzV5GZ.js} +1 -1
  32. chainlit/frontend/dist/assets/Dataframe-DVgwSMU2.js +22 -0
  33. chainlit/frontend/dist/assets/{Facebook-CHEgeJDe.js → Facebook-C0vx6HWv.js} +1 -1
  34. chainlit/frontend/dist/assets/{FilePlayer-BMFA6He5.js → FilePlayer-CdhzeHPP.js} +1 -1
  35. chainlit/frontend/dist/assets/{Kaltura-BS4Q0SKd.js → Kaltura-5iVmeUct.js} +1 -1
  36. chainlit/frontend/dist/assets/{Mixcloud-tLlgZy_i.js → Mixcloud-C2zi77Ex.js} +1 -1
  37. chainlit/frontend/dist/assets/{Mux-Bcz0qNhS.js → Mux-Vkebogdf.js} +1 -1
  38. chainlit/frontend/dist/assets/{Preview-RsJjlwJx.js → Preview-DwY_sEIl.js} +1 -1
  39. chainlit/frontend/dist/assets/{SoundCloud-B9UgR7Bk.js → SoundCloud-CREBXAWo.js} +1 -1
  40. chainlit/frontend/dist/assets/{Streamable-BOgIqbui.js → Streamable-B5Lu25uy.js} +1 -1
  41. chainlit/frontend/dist/assets/{Twitch-CBX_d6nV.js → Twitch-y9iKCcM1.js} +1 -1
  42. chainlit/frontend/dist/assets/{Vidyard-C5HPuozf.js → Vidyard-ClYvcuEu.js} +1 -1
  43. chainlit/frontend/dist/assets/{Vimeo-CHBmywi9.js → Vimeo-D6HvM2jt.js} +1 -1
  44. chainlit/frontend/dist/assets/Wistia-Cu4zZ2Ci.js +1 -0
  45. chainlit/frontend/dist/assets/{YouTube-CA7t0q0j.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-Ba2Cl614.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.2.dist-info → chainlit-2.0.0.dist-info}/METADATA +15 -35
  76. chainlit-2.0.0.dist-info/RECORD +106 -0
  77. chainlit/frontend/dist/assets/Wistia-1Gb23ljh.js +0 -1
  78. chainlit/frontend/dist/assets/index-CwmincdQ.css +0 -1
  79. chainlit/frontend/dist/assets/index-DnjoDoLU.js +0 -723
  80. chainlit-1.3.2.dist-info/RECORD +0 -96
  81. {chainlit-1.3.2.dist-info → chainlit-2.0.0.dist-info}/WHEEL +0 -0
  82. {chainlit-1.3.2.dist-info → chainlit-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -7,8 +7,13 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
7
7
 
8
8
  import aiofiles
9
9
  import aiohttp
10
+ from sqlalchemy import text
11
+ from sqlalchemy.exc import SQLAlchemyError
12
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
13
+ from sqlalchemy.orm import sessionmaker
10
14
 
11
- from chainlit.data.base import BaseDataLayer, BaseStorageClient
15
+ from chainlit.data.base import BaseDataLayer
16
+ from chainlit.data.storage_clients.base import BaseStorageClient
12
17
  from chainlit.data.utils import queue_until_user_message
13
18
  from chainlit.element import ElementDict
14
19
  from chainlit.logger import logger
@@ -23,10 +28,6 @@ from chainlit.types import (
23
28
  ThreadFilter,
24
29
  )
25
30
  from chainlit.user import PersistedUser, User
26
- from sqlalchemy import text
27
- from sqlalchemy.exc import SQLAlchemyError
28
- from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
29
- from sqlalchemy.orm import sessionmaker
30
31
 
31
32
  if TYPE_CHECKING:
32
33
  from chainlit.element import Element, ElementDict
@@ -167,7 +168,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
167
168
  async def create_user(self, user: User) -> Optional[PersistedUser]:
168
169
  if self.show_logger:
169
170
  logger.info(f"SQLAlchemy: create_user, user_identifier={user.identifier}")
170
- existing_user: Optional["PersistedUser"] = await self.get_user(user.identifier)
171
+ existing_user: Optional[PersistedUser] = await self.get_user(user.identifier)
171
172
  user_dict: Dict[str, Any] = {
172
173
  "identifier": str(user.identifier),
173
174
  "metadata": json.dumps(user.metadata) or {},
@@ -438,6 +439,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
438
439
  url=element_dict.get("url"),
439
440
  objectKey=element_dict.get("objectKey"),
440
441
  name=element_dict["name"],
442
+ props=element_dict.get("props"),
441
443
  display=element_dict["display"],
442
444
  size=element_dict.get("size"),
443
445
  language=element_dict.get("language"),
@@ -576,7 +578,6 @@ class SQLAlchemyDataLayer(BaseDataLayer):
576
578
  s."generation" AS step_generation,
577
579
  s."showInput" AS step_showinput,
578
580
  s."language" AS step_language,
579
- s."indent" AS step_indent,
580
581
  f."value" AS feedback_value,
581
582
  f."comment" AS feedback_comment,
582
583
  f."id" AS feedback_id
@@ -664,7 +665,6 @@ class SQLAlchemyDataLayer(BaseDataLayer):
664
665
  generation=step_feedback.get("step_generation"),
665
666
  showInput=step_feedback.get("step_showinput"),
666
667
  language=step_feedback.get("step_language"),
667
- indent=step_feedback.get("step_indent"),
668
668
  feedback=feedback,
669
669
  )
670
670
  # Append the step to the steps list of the corresponding ThreadDict
@@ -688,6 +688,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
688
688
  autoPlay=element.get("element_autoPlay"),
689
689
  playerConfig=element.get("element_playerconfig"),
690
690
  page=element.get("element_page"),
691
+ props=element.get("element_props"),
691
692
  forId=element.get("element_forid"),
692
693
  mime=element.get("element_mime"),
693
694
  )
File without changes
@@ -1,13 +1,13 @@
1
1
  from typing import TYPE_CHECKING, Any, Dict, Optional, Union
2
2
 
3
- import boto3 # type: ignore
4
3
  from azure.storage.filedatalake import (
5
4
  ContentSettings,
6
5
  DataLakeFileClient,
7
6
  DataLakeServiceClient,
8
7
  FileSystemClient,
9
8
  )
10
- from chainlit.data.base import BaseStorageClient
9
+
10
+ from chainlit.data.storage_clients.base import BaseStorageClient
11
11
  from chainlit.logger import logger
12
12
 
13
13
  if TYPE_CHECKING:
@@ -79,34 +79,3 @@ class AzureStorageClient(BaseStorageClient):
79
79
  except Exception as e:
80
80
  logger.warn(f"AzureStorageClient, upload_file error: {e}")
81
81
  return {}
82
-
83
-
84
- class S3StorageClient(BaseStorageClient):
85
- """
86
- Class to enable Amazon S3 storage provider
87
- """
88
-
89
- def __init__(self, bucket: str):
90
- try:
91
- self.bucket = bucket
92
- self.client = boto3.client("s3")
93
- logger.info("S3StorageClient initialized")
94
- except Exception as e:
95
- logger.warn(f"S3StorageClient initialization error: {e}")
96
-
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]:
104
- try:
105
- self.client.put_object(
106
- Bucket=self.bucket, Key=object_key, Body=data, ContentType=mime
107
- )
108
- url = f"https://{self.bucket}.s3.amazonaws.com/{object_key}"
109
- return {"object_key": object_key, "url": url}
110
- except Exception as e:
111
- logger.warn(f"S3StorageClient, upload_file error: {e}")
112
- return {}
@@ -0,0 +1,80 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import Any, Dict, Union
3
+
4
+ from azure.storage.blob import BlobSasPermissions, ContentSettings, generate_blob_sas
5
+ from azure.storage.blob.aio import BlobServiceClient as AsyncBlobServiceClient
6
+
7
+ from chainlit.data.storage_clients.base import EXPIRY_TIME, BaseStorageClient
8
+ from chainlit.logger import logger
9
+
10
+
11
+ class AzureBlobStorageClient(BaseStorageClient):
12
+ def __init__(self, container_name: str, storage_account: str, storage_key: str):
13
+ self.container_name = container_name
14
+ self.storage_account = storage_account
15
+ self.storage_key = storage_key
16
+ connection_string = (
17
+ f"DefaultEndpointsProtocol=https;"
18
+ f"AccountName={storage_account};"
19
+ f"AccountKey={storage_key};"
20
+ f"EndpointSuffix=core.windows.net"
21
+ )
22
+ self.service_client = AsyncBlobServiceClient.from_connection_string(
23
+ connection_string
24
+ )
25
+ logger.info("AzureBlobStorageClient initialized")
26
+
27
+ async def get_read_url(self, object_key: str) -> str:
28
+ if not self.storage_key:
29
+ raise Exception("Not using Azure Storage")
30
+
31
+ sas_permissions = BlobSasPermissions(read=True)
32
+ start_time = datetime.now()
33
+ expiry_time = start_time + timedelta(seconds=EXPIRY_TIME)
34
+
35
+ sas_token = generate_blob_sas(
36
+ account_name=self.storage_account,
37
+ container_name=self.container_name,
38
+ blob_name=object_key,
39
+ account_key=self.storage_key,
40
+ permission=sas_permissions,
41
+ start=start_time,
42
+ expiry=expiry_time,
43
+ )
44
+
45
+ return f"https://{self.storage_account}.blob.core.windows.net/{self.container_name}/{object_key}?{sas_token}"
46
+
47
+ async def upload_file(
48
+ self,
49
+ object_key: str,
50
+ data: Union[bytes, str],
51
+ mime: str = "application/octet-stream",
52
+ overwrite: bool = True,
53
+ ) -> Dict[str, Any]:
54
+ try:
55
+ container_client = self.service_client.get_container_client(
56
+ self.container_name
57
+ )
58
+ blob_client = container_client.get_blob_client(object_key)
59
+
60
+ if isinstance(data, str):
61
+ data = data.encode("utf-8")
62
+
63
+ content_settings = ContentSettings(content_type=mime)
64
+
65
+ await blob_client.upload_blob(
66
+ data, overwrite=overwrite, content_settings=content_settings
67
+ )
68
+
69
+ properties = await blob_client.get_blob_properties()
70
+
71
+ return {
72
+ "path": object_key,
73
+ "size": properties.size,
74
+ "last_modified": properties.last_modified,
75
+ "etag": properties.etag,
76
+ "content_type": properties.content_settings.content_type,
77
+ }
78
+
79
+ except Exception as e:
80
+ raise Exception(f"Failed to upload file to Azure Blob Storage: {e!s}")
@@ -0,0 +1,22 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, Union
3
+
4
+ EXPIRY_TIME = 3600
5
+
6
+
7
+ class BaseStorageClient(ABC):
8
+ """Base class for non-text data persistence like Azure Data Lake, S3, Google Storage, etc."""
9
+
10
+ @abstractmethod
11
+ async def upload_file(
12
+ self,
13
+ object_key: str,
14
+ data: Union[bytes, str],
15
+ mime: str = "application/octet-stream",
16
+ overwrite: bool = True,
17
+ ) -> Dict[str, Any]:
18
+ pass
19
+
20
+ @abstractmethod
21
+ async def get_read_url(self, object_key: str) -> str:
22
+ pass
@@ -0,0 +1,78 @@
1
+ import base64
2
+ from typing import Any, Dict, Union
3
+
4
+ from google.cloud import storage # type: ignore
5
+ from google.oauth2 import service_account
6
+
7
+ from chainlit import make_async
8
+ from chainlit.data.storage_clients.base import EXPIRY_TIME, BaseStorageClient
9
+ from chainlit.logger import logger
10
+
11
+
12
+ class GCSStorageClient(BaseStorageClient):
13
+ def __init__(
14
+ self, project_id: str, client_email: str, private_key: str, bucket_name: str
15
+ ):
16
+ private_key = base64.b64decode(private_key).decode("utf-8")
17
+
18
+ credentials = service_account.Credentials.from_service_account_info(
19
+ {
20
+ "type": "service_account",
21
+ "project_id": project_id,
22
+ "private_key": private_key,
23
+ "client_email": client_email,
24
+ "token_uri": "https://oauth2.googleapis.com/token",
25
+ }
26
+ )
27
+
28
+ self.client = storage.Client(project=project_id, credentials=credentials)
29
+ self.bucket_name = bucket_name
30
+ self.bucket = self.client.bucket(bucket_name)
31
+ logger.info("GCSStorageClient initialized")
32
+
33
+ def sync_get_read_url(self, object_key: str) -> str:
34
+ return self.bucket.blob(object_key).generate_signed_url(
35
+ version="v4", expiration=EXPIRY_TIME, method="GET"
36
+ )
37
+
38
+ async def get_read_url(self, object_key: str) -> str:
39
+ return await make_async(self.sync_get_read_url)(object_key)
40
+
41
+ def sync_upload_file(
42
+ self,
43
+ object_key: str,
44
+ data: Union[bytes, str],
45
+ mime: str = "application/octet-stream",
46
+ overwrite: bool = True,
47
+ ) -> Dict[str, Any]:
48
+ try:
49
+ blob = self.bucket.blob(object_key)
50
+
51
+ if not overwrite and blob.exists():
52
+ raise Exception(
53
+ f"File {object_key} already exists and overwrite is False"
54
+ )
55
+
56
+ if isinstance(data, str):
57
+ data = data.encode("utf-8")
58
+
59
+ blob.upload_from_string(data, content_type=mime)
60
+
61
+ return {
62
+ "object_key": object_key,
63
+ "url": f"gs://{self.bucket_name}/{object_key}",
64
+ }
65
+
66
+ except Exception as e:
67
+ raise Exception(f"Failed to upload file to GCS: {e!s}")
68
+
69
+ async def upload_file(
70
+ self,
71
+ object_key: str,
72
+ data: Union[bytes, str],
73
+ mime: str = "application/octet-stream",
74
+ overwrite: bool = True,
75
+ ) -> Dict[str, Any]:
76
+ return await make_async(self.sync_upload_file)(
77
+ object_key, data, mime, overwrite
78
+ )
@@ -0,0 +1,49 @@
1
+ from typing import Any, Dict, Union
2
+
3
+ import boto3 # type: ignore
4
+
5
+ from chainlit.data.storage_clients.base import EXPIRY_TIME, BaseStorageClient
6
+ from chainlit.logger import logger
7
+
8
+
9
+ class S3StorageClient(BaseStorageClient):
10
+ """
11
+ Class to enable Amazon S3 storage provider
12
+ """
13
+
14
+ def __init__(self, bucket: str, **kwargs: Any):
15
+ try:
16
+ self.bucket = bucket
17
+ self.client = boto3.client("s3", **kwargs)
18
+ logger.info("S3StorageClient initialized")
19
+ except Exception as e:
20
+ logger.warn(f"S3StorageClient initialization error: {e}")
21
+
22
+ async def get_read_url(self, object_key: str) -> str:
23
+ try:
24
+ url = self.client.generate_presigned_url(
25
+ "get_object",
26
+ Params={"Bucket": self.bucket, "Key": object_key},
27
+ ExpiresIn=EXPIRY_TIME,
28
+ )
29
+ return url
30
+ except Exception as e:
31
+ logger.warn(f"S3StorageClient, get_read_url error: {e}")
32
+ return object_key
33
+
34
+ async def upload_file(
35
+ self,
36
+ object_key: str,
37
+ data: Union[bytes, str],
38
+ mime: str = "application/octet-stream",
39
+ overwrite: bool = True,
40
+ ) -> Dict[str, Any]:
41
+ try:
42
+ self.client.put_object(
43
+ Bucket=self.bucket, Key=object_key, Body=data, ContentType=mime
44
+ )
45
+ url = f"https://{self.bucket}.s3.amazonaws.com/{object_key}"
46
+ return {"object_key": object_key, "url": url}
47
+ except Exception as e:
48
+ logger.warn(f"S3StorageClient, upload_file error: {e}")
49
+ return {}
@@ -1,6 +1,6 @@
1
- try:
2
- import discord
3
- except ModuleNotFoundError:
1
+ import importlib.util
2
+
3
+ if importlib.util.find_spec("discord") is None:
4
4
  raise ValueError(
5
- "The discord package is required to integrate Chainlit with a Slack app. Run `pip install discord --upgrade`"
5
+ "The discord package is required to integrate Chainlit with a Discord app. Run `pip install discord --upgrade`"
6
6
  )
chainlit/discord/app.py CHANGED
@@ -12,6 +12,8 @@ if TYPE_CHECKING:
12
12
  import discord
13
13
  import filetype
14
14
  import httpx
15
+ from discord.ui import Button, View
16
+
15
17
  from chainlit.config import config
16
18
  from chainlit.context import ChainlitContext, HTTPSession, context, context_var
17
19
  from chainlit.data import get_data_layer
@@ -23,7 +25,6 @@ from chainlit.telemetry import trace
23
25
  from chainlit.types import Feedback
24
26
  from chainlit.user import PersistedUser, User
25
27
  from chainlit.user_session import user_session
26
- from discord.ui import Button, View
27
28
 
28
29
 
29
30
  class FeedbackView(View):
chainlit/element.py CHANGED
@@ -16,13 +16,15 @@ from typing import (
16
16
  )
17
17
 
18
18
  import filetype
19
+ from pydantic import Field
20
+ from pydantic.dataclasses import dataclass
21
+ from syncer import asyncio
22
+
19
23
  from chainlit.context import context
20
24
  from chainlit.data import get_data_layer
21
25
  from chainlit.logger import logger
22
26
  from chainlit.telemetry import trace_event
23
27
  from chainlit.types import FileDict
24
- from pydantic.dataclasses import Field, dataclass
25
- from syncer import asyncio
26
28
 
27
29
  mime_types = {
28
30
  "text": "text/plain",
@@ -31,7 +33,16 @@ mime_types = {
31
33
  }
32
34
 
33
35
  ElementType = Literal[
34
- "image", "text", "pdf", "tasklist", "audio", "video", "file", "plotly", "component"
36
+ "image",
37
+ "text",
38
+ "pdf",
39
+ "tasklist",
40
+ "audio",
41
+ "video",
42
+ "file",
43
+ "plotly",
44
+ "dataframe",
45
+ "custom",
35
46
  ]
36
47
  ElementDisplay = Literal["inline", "side", "page"]
37
48
  ElementSize = Literal["small", "medium", "large"]
@@ -49,6 +60,7 @@ class ElementDict(TypedDict):
49
60
  size: Optional[ElementSize]
50
61
  language: Optional[str]
51
62
  page: Optional[int]
63
+ props: Optional[Dict]
52
64
  autoPlay: Optional[bool]
53
65
  playerConfig: Optional[dict]
54
66
  forId: Optional[str]
@@ -106,6 +118,7 @@ class Element:
106
118
  "display": self.display,
107
119
  "objectKey": getattr(self, "object_key", None),
108
120
  "size": getattr(self, "size", None),
121
+ "props": getattr(self, "props", None),
109
122
  "page": getattr(self, "page", None),
110
123
  "autoPlay": getattr(self, "auto_play", None),
111
124
  "playerConfig": getattr(self, "player_config", None),
@@ -145,7 +158,7 @@ class Element:
145
158
  try:
146
159
  asyncio.create_task(data_layer.create_element(self))
147
160
  except Exception as e:
148
- logger.error(f"Failed to create element: {str(e)}")
161
+ logger.error(f"Failed to create element: {e!s}")
149
162
  if not self.url and (not self.chainlit_key or self.updatable):
150
163
  file_dict = await context.session.persist_file(
151
164
  name=self.name,
@@ -343,8 +356,7 @@ class Plotly(Element):
343
356
  content: str = ""
344
357
 
345
358
  def __post_init__(self) -> None:
346
- from plotly import graph_objects as go
347
- from plotly import io as pio
359
+ from plotly import graph_objects as go, io as pio
348
360
 
349
361
  if not isinstance(self.figure, go.Figure):
350
362
  raise TypeError("figure must be a plotly.graph_objects.Figure")
@@ -359,14 +371,34 @@ class Plotly(Element):
359
371
 
360
372
 
361
373
  @dataclass
362
- class Component(Element):
363
- """Useful to send a custom component to the UI."""
374
+ class Dataframe(Element):
375
+ """Useful to send a pandas DataFrame to the UI."""
376
+
377
+ type: ClassVar[ElementType] = "dataframe"
378
+ size: ElementSize = "large"
379
+ data: Any = None # The type is Any because it is checked in __post_init__.
380
+
381
+ def __post_init__(self) -> None:
382
+ """Ensures the data is a pandas DataFrame and converts it to JSON."""
383
+ from pandas import DataFrame
384
+
385
+ if not isinstance(self.data, DataFrame):
386
+ raise TypeError("data must be a pandas.DataFrame")
364
387
 
365
- type: ClassVar[ElementType] = "component"
388
+ self.content = self.data.to_json(orient="split", date_format="iso")
389
+ super().__post_init__()
390
+
391
+
392
+ @dataclass
393
+ class CustomElement(Element):
394
+ """Useful to send a custom element to the UI."""
395
+
396
+ type: ClassVar[ElementType] = "custom"
366
397
  mime: str = "application/json"
367
398
  props: Dict = Field(default_factory=dict)
368
399
 
369
400
  def __post_init__(self) -> None:
370
401
  self.content = json.dumps(self.props)
402
+ self.updatable = True
371
403
 
372
404
  super().__post_init__()
chainlit/emitter.py CHANGED
@@ -2,6 +2,9 @@ import asyncio
2
2
  import uuid
3
3
  from typing import Any, Dict, List, Literal, Optional, Union, cast
4
4
 
5
+ from literalai.helper import utc_now
6
+ from socketio.exceptions import TimeoutError
7
+
5
8
  from chainlit.chat_context import chat_context
6
9
  from chainlit.config import config
7
10
  from chainlit.data import get_data_layer
@@ -16,11 +19,10 @@ from chainlit.types import (
16
19
  FileDict,
17
20
  FileReference,
18
21
  MessagePayload,
22
+ OutputAudioChunk,
19
23
  ThreadDict,
20
24
  )
21
25
  from chainlit.user import PersistedUser
22
- from literalai.helper import utc_now
23
- from socketio.exceptions import TimeoutError
24
26
 
25
27
 
26
28
  class BaseChainlitEmitter:
@@ -52,6 +54,18 @@ class BaseChainlitEmitter:
52
54
  """Stub method to send an element to the UI."""
53
55
  pass
54
56
 
57
+ async def update_audio_connection(self, state: Literal["on", "off"]):
58
+ """Audio connection signaling."""
59
+ pass
60
+
61
+ async def send_audio_chunk(self, chunk: OutputAudioChunk):
62
+ """Stub method to send an audio chunk to the UI."""
63
+ pass
64
+
65
+ async def send_audio_interrupt(self):
66
+ """Stub method to interrupt the current audio response."""
67
+ pass
68
+
55
69
  async def send_step(self, step_dict: StepDict):
56
70
  """Stub method to send a message to the UI."""
57
71
  pass
@@ -114,10 +128,8 @@ class BaseChainlitEmitter:
114
128
  """Stub method to set chat settings."""
115
129
  pass
116
130
 
117
- async def send_action_response(
118
- self, id: str, status: bool, response: Optional[str] = None
119
- ):
120
- """Send an action response to the UI."""
131
+ async def send_window_message(self, data: Any):
132
+ """Stub method to send custom data to the host window."""
121
133
  pass
122
134
 
123
135
 
@@ -157,6 +169,18 @@ class ChainlitEmitter(BaseChainlitEmitter):
157
169
  """Send a thread to the UI to resume it"""
158
170
  return self.emit("resume_thread", thread_dict)
159
171
 
172
+ async def update_audio_connection(self, state: Literal["on", "off"]):
173
+ """Audio connection signaling."""
174
+ await self.emit("audio_connection", state)
175
+
176
+ async def send_audio_chunk(self, chunk: OutputAudioChunk):
177
+ """Send an audio chunk to the UI."""
178
+ await self.emit("audio_chunk", chunk)
179
+
180
+ async def send_audio_interrupt(self):
181
+ """Method to interrupt the current audio response."""
182
+ await self.emit("audio_interrupt", {})
183
+
160
184
  async def send_element(self, element_dict: ElementDict):
161
185
  """Stub method to send an element to the UI."""
162
186
  await self.emit("element", element_dict)
@@ -258,9 +282,9 @@ class ChainlitEmitter(BaseChainlitEmitter):
258
282
  # End the task temporarily so that the User can answer the prompt
259
283
  await self.task_end()
260
284
 
261
- final_res: Optional[
262
- Union["StepDict", "AskActionResponse", List["FileDict"]]
263
- ] = None
285
+ final_res: Optional[Union[StepDict, AskActionResponse, List[FileDict]]] = (
286
+ None
287
+ )
264
288
 
265
289
  if user_res:
266
290
  interaction: Union[str, None] = None
@@ -295,7 +319,7 @@ class ChainlitEmitter(BaseChainlitEmitter):
295
319
  elif spec.type == "action":
296
320
  action_res = cast(AskActionResponse, user_res)
297
321
  final_res = action_res
298
- interaction = action_res["value"]
322
+ interaction = action_res["name"]
299
323
 
300
324
  if not self.session.has_first_interaction and interaction:
301
325
  self.session.has_first_interaction = True
@@ -361,9 +385,6 @@ class ChainlitEmitter(BaseChainlitEmitter):
361
385
  def set_chat_settings(self, settings: Dict[str, Any]):
362
386
  self.session.chat_settings = settings
363
387
 
364
- def send_action_response(
365
- self, id: str, status: bool, response: Optional[str] = None
366
- ):
367
- return self.emit(
368
- "action_response", {"id": id, "status": status, "response": response}
369
- )
388
+ def send_window_message(self, data: Any):
389
+ """Send custom data to the host window."""
390
+ return self.emit("window_message", data)
@@ -1 +1 @@
1
- import{g as P,r as v,u as D,a as O}from"./index-DnjoDoLU.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(!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 N="https://api.dmcdn.net/all.js",x="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)(N,x,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
+ import{n as P,r as v,o as D,q as O}from"./index-CI4qFOt5.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(!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 N="https://api.dmcdn.net/all.js",x="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)(N,x,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};