chainlit 0.2.110__py3-none-any.whl → 0.3.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/client.py CHANGED
@@ -1,8 +1,11 @@
1
1
  from typing import Dict, Any, Optional
2
- from python_graphql_client import GraphqlClient
3
2
  from abc import ABC, abstractmethod
4
3
  import uuid
5
- import requests
4
+
5
+ import asyncio
6
+ import aiohttp
7
+ from python_graphql_client import GraphqlClient
8
+
6
9
  from chainlit.types import ElementType, ElementSize
7
10
  from chainlit.logger import logger
8
11
  from chainlit.config import config
@@ -13,31 +16,31 @@ class BaseClient(ABC):
13
16
  session_id: str
14
17
 
15
18
  @abstractmethod
16
- def is_project_member(self, access_token: str) -> bool:
19
+ async def is_project_member(self, access_token: str) -> bool:
17
20
  pass
18
21
 
19
22
  @abstractmethod
20
- def create_conversation(self, session_id: str) -> int:
23
+ async def create_conversation(self, session_id: str) -> int:
21
24
  pass
22
25
 
23
26
  @abstractmethod
24
- def create_message(self, variables: Dict[str, Any]) -> int:
27
+ async def create_message(self, variables: Dict[str, Any]) -> int:
25
28
  pass
26
29
 
27
30
  @abstractmethod
28
- def update_message(self, message_id: int, variables: Dict[str, Any]) -> bool:
31
+ async def update_message(self, message_id: int, variables: Dict[str, Any]) -> bool:
29
32
  pass
30
33
 
31
34
  @abstractmethod
32
- def delete_message(self, message_id: int) -> bool:
35
+ async def delete_message(self, message_id: int) -> bool:
33
36
  pass
34
37
 
35
38
  @abstractmethod
36
- def upload_element(self, content: bytes, mime: str) -> int:
39
+ async def upload_element(self, content: bytes, mime: str) -> str:
37
40
  pass
38
41
 
39
42
  @abstractmethod
40
- def create_element(
43
+ async def create_element(
41
44
  self,
42
45
  type: ElementType,
43
46
  url: str,
@@ -50,16 +53,23 @@ class BaseClient(ABC):
50
53
  pass
51
54
 
52
55
 
56
+ conversation_lock = asyncio.Lock()
57
+
58
+
53
59
  class CloudClient(BaseClient):
54
60
  conversation_id: Optional[str] = None
55
61
 
56
- def __init__(self, project_id: str, session_id: str, access_token: str, url: str):
62
+ def __init__(self, project_id: str, session_id: str, access_token: str):
57
63
  self.project_id = project_id
58
64
  self.session_id = session_id
59
- self.url = url
60
- self.headers = {"Authorization": access_token}
61
- graphql_endpoint = f"{url}/api/graphql"
62
- self.client = GraphqlClient(endpoint=graphql_endpoint, headers=self.headers)
65
+ self.headers = {
66
+ "Authorization": access_token,
67
+ "content-type": "application/json",
68
+ }
69
+ graphql_endpoint = f"{config.chainlit_server}/api/graphql"
70
+ self.graphql_client = GraphqlClient(
71
+ endpoint=graphql_endpoint, headers=self.headers
72
+ )
63
73
 
64
74
  def query(self, query: str, variables: Dict[str, Any] = {}) -> Dict[str, Any]:
65
75
  """
@@ -69,7 +79,7 @@ class CloudClient(BaseClient):
69
79
  :param variables: A dictionary of variables for the query.
70
80
  :return: The response data as a dictionary.
71
81
  """
72
- return self.client.execute(query=query, variables=variables)
82
+ return self.graphql_client.execute_async(query=query, variables=variables)
73
83
 
74
84
  def check_for_errors(self, response: Dict[str, Any]):
75
85
  if "errors" in response:
@@ -85,50 +95,53 @@ class CloudClient(BaseClient):
85
95
  :param variables: A dictionary of variables for the mutation.
86
96
  :return: The response data as a dictionary.
87
97
  """
88
- return self.client.execute(query=mutation, variables=variables)
89
-
90
- def is_project_member(self) -> bool:
91
- try:
92
- headers = {
93
- "content-type": "application/json",
94
- "Authorization": self.headers["Authorization"],
98
+ return self.graphql_client.execute_async(query=mutation, variables=variables)
99
+
100
+ async def is_project_member(self) -> bool:
101
+ data = {"projectId": self.project_id}
102
+ async with aiohttp.ClientSession() as session:
103
+ async with session.post(
104
+ f"{config.chainlit_server}/api/role",
105
+ json=data,
106
+ headers=self.headers,
107
+ ) as r:
108
+ if not r.ok:
109
+ reason = await r.text()
110
+ logger.error(f"Failed to get user role. {r.status}: {reason}")
111
+ return False
112
+ json = await r.json()
113
+ role = json.get("role", "ANONYMOUS")
114
+ return role != "ANONYMOUS"
115
+
116
+ async def create_conversation(self, session_id: str) -> int:
117
+ # If we run multiple send concurrently, we need to make sure we don't create multiple conversations.
118
+ async with conversation_lock:
119
+ if self.conversation_id:
120
+ return self.conversation_id
121
+
122
+ mutation = """
123
+ mutation ($projectId: String!, $sessionId: String!) {
124
+ createConversation(projectId: $projectId, sessionId: $sessionId) {
125
+ id
126
+ }
95
127
  }
96
- data = {"projectId": self.project_id}
97
- response = requests.post(
98
- f"{config.chainlit_server}/api/role", headers=headers, json=data
99
- )
100
-
101
- role = response.json().get("role", "ANONYMOUS")
102
- return role != "ANONYMOUS"
103
- except Exception as e:
104
- logger.exception(e)
105
- return False
128
+ """
129
+ variables = {"projectId": self.project_id, "sessionId": session_id}
130
+ res = await self.mutation(mutation, variables)
106
131
 
107
- def create_conversation(self, session_id: str) -> int:
108
- mutation = """
109
- mutation ($projectId: String!, $sessionId: String!) {
110
- createConversation(projectId: $projectId, sessionId: $sessionId) {
111
- id
112
- }
113
- }
114
- """
115
- variables = {"projectId": self.project_id, "sessionId": session_id}
116
- res = self.mutation(mutation, variables)
117
-
118
- if self.check_for_errors(res):
119
- logger.warning("Could not create conversation.")
120
- return None
132
+ if self.check_for_errors(res):
133
+ logger.warning("Could not create conversation.")
134
+ return None
121
135
 
122
- return int(res["data"]["createConversation"]["id"])
136
+ return int(res["data"]["createConversation"]["id"])
123
137
 
124
- def get_conversation_id(self):
125
- if not self.conversation_id:
126
- self.conversation_id = self.create_conversation(self.session_id)
138
+ async def get_conversation_id(self):
139
+ self.conversation_id = await self.create_conversation(self.session_id)
127
140
 
128
141
  return self.conversation_id
129
142
 
130
- def create_message(self, variables: Dict[str, Any]) -> int:
131
- c_id = self.get_conversation_id()
143
+ async def create_message(self, variables: Dict[str, Any]) -> int:
144
+ c_id = await self.get_conversation_id()
132
145
 
133
146
  if not c_id:
134
147
  logger.warning("Missing conversation ID, could not persist the message.")
@@ -137,21 +150,20 @@ class CloudClient(BaseClient):
137
150
  variables["conversationId"] = c_id
138
151
 
139
152
  mutation = """
140
- mutation ($conversationId: ID!, $author: String!, $content: String!, $language: String, $prompt: String, $llmSettings: Json, $isError: Boolean, $indent: Int, $authorIsUser: Boolean, $waitForAnswer: Boolean) {
141
- createMessage(conversationId: $conversationId, author: $author, content: $content, language: $language, prompt: $prompt, llmSettings: $llmSettings, isError: $isError, indent: $indent, authorIsUser: $authorIsUser, waitForAnswer: $waitForAnswer) {
153
+ mutation ($conversationId: ID!, $author: String!, $content: String!, $language: String, $prompt: String, $llmSettings: Json, $isError: Boolean, $indent: Int, $authorIsUser: Boolean, $waitForAnswer: Boolean, $createdAt: Float) {
154
+ createMessage(conversationId: $conversationId, author: $author, content: $content, language: $language, prompt: $prompt, llmSettings: $llmSettings, isError: $isError, indent: $indent, authorIsUser: $authorIsUser, waitForAnswer: $waitForAnswer, createdAt: $createdAt) {
142
155
  id
143
156
  }
144
157
  }
145
158
  """
146
- res = self.mutation(mutation, variables)
147
-
159
+ res = await self.mutation(mutation, variables)
148
160
  if self.check_for_errors(res):
149
161
  logger.warning("Could not create message.")
150
162
  return None
151
163
 
152
164
  return int(res["data"]["createMessage"]["id"])
153
165
 
154
- def update_message(self, message_id: int, variables: Dict[str, Any]) -> bool:
166
+ async def update_message(self, message_id: int, variables: Dict[str, Any]) -> bool:
155
167
  mutation = """
156
168
  mutation ($messageId: ID!, $author: String!, $content: String!, $language: String, $prompt: String, $llmSettings: Json) {
157
169
  updateMessage(messageId: $messageId, author: $author, content: $content, language: $language, prompt: $prompt, llmSettings: $llmSettings) {
@@ -160,7 +172,7 @@ class CloudClient(BaseClient):
160
172
  }
161
173
  """
162
174
  variables["messageId"] = message_id
163
- res = self.mutation(mutation, variables)
175
+ res = await self.mutation(mutation, variables)
164
176
 
165
177
  if self.check_for_errors(res):
166
178
  logger.warning("Could not update message.")
@@ -168,7 +180,7 @@ class CloudClient(BaseClient):
168
180
 
169
181
  return True
170
182
 
171
- def delete_message(self, message_id: int) -> bool:
183
+ async def delete_message(self, message_id: int) -> bool:
172
184
  mutation = """
173
185
  mutation ($messageId: ID!) {
174
186
  deleteMessage(messageId: $messageId) {
@@ -176,7 +188,7 @@ class CloudClient(BaseClient):
176
188
  }
177
189
  }
178
190
  """
179
- res = self.mutation(mutation, {"messageId": message_id})
191
+ res = await self.mutation(mutation, {"messageId": message_id})
180
192
 
181
193
  if self.check_for_errors(res):
182
194
  logger.warning("Could not delete message.")
@@ -184,7 +196,7 @@ class CloudClient(BaseClient):
184
196
 
185
197
  return True
186
198
 
187
- def create_element(
199
+ async def create_element(
188
200
  self,
189
201
  type: ElementType,
190
202
  url: str,
@@ -194,7 +206,7 @@ class CloudClient(BaseClient):
194
206
  language: str = None,
195
207
  for_id: str = None,
196
208
  ) -> Dict[str, Any]:
197
- c_id = self.get_conversation_id()
209
+ c_id = await self.get_conversation_id()
198
210
 
199
211
  if not c_id:
200
212
  logger.warning("Missing conversation ID, could not persist the element.")
@@ -224,7 +236,7 @@ class CloudClient(BaseClient):
224
236
  "language": language,
225
237
  "forId": for_id,
226
238
  }
227
- res = self.mutation(mutation, variables)
239
+ res = await self.mutation(mutation, variables)
228
240
 
229
241
  if self.check_for_errors(res):
230
242
  logger.warning("Could not persist element.")
@@ -232,34 +244,44 @@ class CloudClient(BaseClient):
232
244
 
233
245
  return res["data"]["createElement"]
234
246
 
235
- def upload_element(self, content: bytes, mime: str) -> str:
247
+ async def upload_element(self, content: bytes, mime: str) -> str:
236
248
  id = f"{uuid.uuid4()}"
237
- url = f"{self.url}/api/upload/file"
238
- body = {"projectId": self.project_id, "fileName": id}
239
- if mime:
240
- body["contentType"] = mime
249
+ body = {"projectId": self.project_id, "fileName": id, "contentType": mime}
250
+
251
+ path = f"/api/upload/file"
252
+
253
+ async with aiohttp.ClientSession() as session:
254
+ async with session.post(
255
+ f"{config.chainlit_server}{path}",
256
+ json=body,
257
+ headers=self.headers,
258
+ ) as r:
259
+ if not r.ok:
260
+ reason = await r.text()
261
+ logger.error(f"Failed to upload file: {reason}")
262
+ return ""
263
+ json_res = await r.json()
241
264
 
242
- res = requests.post(url, json=body, headers=self.headers)
243
-
244
- if not res.ok:
245
- logger.error(f"Failed to upload file: {res.text}")
246
- return ""
247
-
248
- json_res = res.json()
249
265
  upload_details = json_res["post"]
250
266
  permanent_url = json_res["permanentUrl"]
251
267
 
252
- files = {"file": content}
253
-
254
- upload_response = requests.post(
255
- upload_details["url"],
256
- data=upload_details["fields"],
257
- files=files,
258
- )
259
-
260
- if not upload_response.ok:
261
- logger.error(f"Failed to upload file: {upload_response.text}")
262
- return ""
263
-
264
- url = f'{upload_details["url"]}/{upload_details["fields"]["key"]}'
265
- return permanent_url
268
+ form_data = aiohttp.FormData()
269
+
270
+ # Add fields to the form_data
271
+ for field_name, field_value in upload_details["fields"].items():
272
+ form_data.add_field(field_name, field_value)
273
+
274
+ # Add file to the form_data
275
+ form_data.add_field("file", content, content_type="multipart/form-data")
276
+ async with aiohttp.ClientSession() as session:
277
+ async with session.post(
278
+ upload_details["url"],
279
+ data=form_data,
280
+ ) as upload_response:
281
+ if not upload_response.ok:
282
+ reason = await upload_response.text()
283
+ logger.error(f"Failed to upload file: {reason}")
284
+ return ""
285
+
286
+ url = f'{upload_details["url"]}/{upload_details["fields"]["key"]}'
287
+ return permanent_url
chainlit/config.py CHANGED
@@ -9,11 +9,12 @@ from chainlit.logger import logger
9
9
  if TYPE_CHECKING:
10
10
  from chainlit.action import Action
11
11
 
12
+ PACKAGE_ROOT = os.path.dirname(__file__)
12
13
 
13
14
  # Get the directory the script is running from
14
- root = os.getcwd()
15
+ APP_ROOT = os.getcwd()
15
16
 
16
- config_dir = os.path.join(root, ".chainlit")
17
+ config_dir = os.path.join(APP_ROOT, ".chainlit")
17
18
  config_file = os.path.join(config_dir, "config.toml")
18
19
 
19
20
  # Default config file created if none exists
@@ -53,8 +54,24 @@ chainlit_prod_url = os.environ.get("CHAINLIT_PROD_URL")
53
54
  chainlit_server = "https://cloud.chainlit.io"
54
55
 
55
56
 
57
+ DEFAULT_HOST = "0.0.0.0"
58
+ DEFAULT_PORT = 8000
59
+
60
+
61
+ @dataclass()
62
+ class RunSettings:
63
+ host: str = DEFAULT_HOST
64
+ port: int = DEFAULT_PORT
65
+ headless: bool = False
66
+ watch: bool = False
67
+ no_cache: bool = False
68
+ debug: bool = False
69
+ ci: bool = False
70
+
71
+
56
72
  @dataclass()
57
73
  class ChainlitConfig:
74
+ run_settings: RunSettings
58
75
  # Chainlit server URL. Used only for cloud features
59
76
  chainlit_server: str
60
77
  # Name of the app and chatbot. Used as the default message author.
@@ -74,7 +91,7 @@ class ChainlitConfig:
74
91
  # Developer defined callbacks for each action. Key is the action name, value is the callback function.
75
92
  action_callbacks: Dict[str, Callable[["Action"], Any]]
76
93
  # Directory where the Chainlit project is located
77
- root = root
94
+ root = APP_ROOT
78
95
  # The url of the deployed app. Only set if the app is deployed.
79
96
  chainlit_prod_url = chainlit_prod_url
80
97
  # Link to your github repo. This will add a github button in the UI's header.
@@ -91,6 +108,7 @@ class ChainlitConfig:
91
108
  on_stop: Optional[Callable[[], Any]] = None
92
109
  on_chat_start: Optional[Callable[[], Any]] = None
93
110
  on_message: Optional[Callable[[str], Any]] = None
111
+ lc_agent_is_async: Optional[bool] = None
94
112
  lc_run: Optional[Callable[[Any, str], str]] = None
95
113
  lc_postprocess: Optional[Callable[[Any], str]] = None
96
114
  lc_factory: Optional[Callable[[], Any]] = None
@@ -174,6 +192,7 @@ def load_config():
174
192
  lc_cache_path = os.path.join(config_dir, ".langchain.db")
175
193
 
176
194
  config = ChainlitConfig(
195
+ run_settings=RunSettings(),
177
196
  action_callbacks={},
178
197
  github=github,
179
198
  request_limit=request_limit,
chainlit/element.py CHANGED
@@ -1,97 +1,75 @@
1
1
  from pydantic.dataclasses import dataclass
2
2
  from dataclasses_json import dataclass_json
3
- from typing import Dict
3
+ from typing import Dict, Union
4
4
  import uuid
5
- from abc import ABC, abstractmethod
6
- from chainlit.sdk import get_sdk, BaseClient
5
+ import aiofiles
6
+
7
+ from chainlit.emitter import get_emitter, BaseClient
7
8
  from chainlit.telemetry import trace_event
8
9
  from chainlit.types import ElementType, ElementDisplay, ElementSize
9
- from base64 import b64encode, b64decode
10
- from urllib import parse as urlparse
10
+
11
+ type_to_mime = {
12
+ "image": "binary/octet-stream",
13
+ "text": "text/plain",
14
+ "pdf": "application/pdf",
15
+ }
11
16
 
12
17
 
13
18
  @dataclass_json
14
19
  @dataclass
15
- class Element(ABC):
20
+ class Element:
21
+ # Name of the element, this will be used to reference the element in the UI.
16
22
  name: str
23
+ # The type of the element. This will be used to determine how to display the element in the UI.
17
24
  type: ElementType
25
+ # Controls how the image element should be displayed in the UI. Choices are “side” (default), “inline”, or “page”.
18
26
  display: ElementDisplay = "side"
27
+ # The URL of the element if already hosted somehwere else.
28
+ url: str = None
29
+ # The local path of the element.
30
+ path: str = None
31
+ # The byte content of the element.
32
+ content: bytes = None
33
+ # The ID of the element. This is set automatically when the element is sent to the UI if cloud is enabled.
19
34
  id: int = None
35
+ # The ID of the element if cloud is disabled.
20
36
  tempId: str = None
37
+ # The ID of the message this element is associated with.
21
38
  forId: str = None
22
39
 
23
40
  def __post_init__(self) -> None:
24
41
  trace_event(f"init {self.__class__.__name__}")
42
+ self.emitter = get_emitter()
43
+ if not self.emitter:
44
+ raise RuntimeError("Element should be instantiated in a Chainlit context")
25
45
 
26
- @abstractmethod
27
- def persist(self, client: BaseClient, for_id: str = None) -> Dict:
28
- pass
29
-
30
- def before_emit(self, element: Dict) -> Dict:
31
- return element
46
+ if not self.url and not self.path and not self.content:
47
+ raise ValueError("Must provide url, path or content to instantiate element")
32
48
 
33
- def send(self, for_id: str = None):
34
- sdk = get_sdk()
35
-
36
- element = None
37
-
38
- # Cloud is enabled, upload the element to S3
39
- if sdk.client:
40
- element = self.persist(sdk.client, for_id)
41
- self.id = element["id"]
42
-
43
- if not element:
44
- self.tempId = uuid.uuid4().hex
45
- element = self.to_dict()
46
- if for_id:
47
- element["forId"] = for_id
48
-
49
- if sdk.emit and element:
50
- trace_event(f"send {self.__class__.__name__}")
51
- element = self.before_emit(element)
52
- sdk.emit("element", element)
49
+ self.tempId = uuid.uuid4().hex
53
50
 
51
+ async def preprocess_content(self):
52
+ pass
54
53
 
55
- @dataclass
56
- class LocalElement(Element):
57
- content: bytes = None
54
+ async def load(self):
55
+ if self.path:
56
+ async with aiofiles.open(self.path, "rb") as f:
57
+ self.content = await f.read()
58
+ await self.preprocess_content()
59
+ elif self.content:
60
+ await self.preprocess_content()
61
+ else:
62
+ raise ValueError("Must provide path or content to load element")
58
63
 
59
- def persist(self, client: BaseClient, for_id: str = None):
60
- if not self.content:
61
- raise ValueError("Must provide content")
62
- url = client.upload_element(content=self.content, mime=None)
63
- if url:
64
- size = getattr(self, "size", None)
65
- language = getattr(self, "language", None)
66
- element = client.create_element(
67
- name=self.name,
68
- url=url,
69
- type=self.type,
70
- display=self.display,
71
- size=size,
72
- language=language,
73
- for_id=for_id,
64
+ async def persist(self, client: BaseClient, for_id: str = None):
65
+ if not self.url and self.content:
66
+ self.url = await client.upload_element(
67
+ content=self.content, mime=type_to_mime[self.type]
74
68
  )
75
- return element
76
-
77
-
78
- @dataclass
79
- class RemoteElementBase:
80
- url: str
81
69
 
82
-
83
- @dataclass
84
- class ImageBase:
85
- type: ElementType = "image"
86
- size: ElementSize = "medium"
87
-
88
-
89
- @dataclass
90
- class RemoteElement(Element, RemoteElementBase):
91
- def persist(self, client: BaseClient, for_id: str = None):
92
70
  size = getattr(self, "size", None)
93
71
  language = getattr(self, "language", None)
94
- element = client.create_element(
72
+ element = await client.create_element(
95
73
  name=self.name,
96
74
  url=self.url,
97
75
  type=self.type,
@@ -102,47 +80,73 @@ class RemoteElement(Element, RemoteElementBase):
102
80
  )
103
81
  return element
104
82
 
83
+ async def before_emit(self, element: Dict) -> Dict:
84
+ return element
105
85
 
106
- @dataclass
107
- class LocalImage(ImageBase, LocalElement):
108
- """Useful to send an image living on the local filesystem to the UI."""
86
+ async def send(self, for_id: str = None):
87
+ element = None
109
88
 
110
- path: str = None
89
+ if not self.content and not self.url and self.path:
90
+ await self.load()
111
91
 
112
- def __post_init__(self):
113
- if self.path:
114
- with open(self.path, "rb") as f:
115
- self.content = f.read()
116
- elif self.content:
117
- self.content = self.content
118
- else:
119
- raise ValueError("Must provide either path or content")
92
+ # Cloud is enabled, upload the element to S3
93
+ if self.emitter.client and not self.id:
94
+ element = await self.persist(self.emitter.client, for_id)
95
+ self.id = element["id"]
120
96
 
97
+ elif not self.url and not self.content:
98
+ raise ValueError("Must provide url or content to send element")
99
+
100
+ element = self.to_dict()
101
+ if for_id:
102
+ element["forId"] = for_id
103
+
104
+ if self.emitter.emit and element:
105
+ trace_event(f"send {self.__class__.__name__}")
106
+ element = await self.before_emit(element)
107
+ await self.emitter.emit("element", element)
121
108
 
122
- @dataclass
123
- class RemoteImage(ImageBase, RemoteElement):
124
- """Useful to send an image based on an URL to the UI."""
125
109
 
126
- pass
110
+ @dataclass
111
+ class Image(Element):
112
+ type: ElementType = "image"
113
+ size: ElementSize = "medium"
127
114
 
128
115
 
129
116
  @dataclass
130
- class TextBase:
131
- text: str
117
+ class Avatar(Element):
118
+ type: ElementType = "avatar"
119
+
120
+ async def send(self):
121
+ element = None
122
+
123
+ if not self.content and not self.url and self.path:
124
+ await self.load()
125
+
126
+ if not self.url and not self.content:
127
+ raise ValueError("Must provide url or content to send element")
128
+
129
+ element = self.to_dict()
130
+
131
+ if self.emitter.emit and element:
132
+ trace_event(f"send {self.__class__.__name__}")
133
+ element = await self.before_emit(element)
134
+ await self.emitter.emit("element", element)
132
135
 
133
136
 
134
137
  @dataclass
135
- class Text(LocalElement, TextBase):
138
+ class Text(Element):
136
139
  """Useful to send a text (not a message) to the UI."""
137
140
 
141
+ content: Union[str, bytes] = None
138
142
  type: ElementType = "text"
139
- content = bytes("", "utf-8")
140
143
  language: str = None
141
144
 
142
- def __post_init__(self):
143
- self.content = bytes(self.text, "utf-8")
145
+ async def preprocess_content(self):
146
+ if isinstance(self.content, str):
147
+ self.content = self.content.encode("utf-8")
144
148
 
145
- def before_emit(self, text_element):
149
+ async def before_emit(self, text_element):
146
150
  if "content" in text_element and isinstance(text_element["content"], bytes):
147
151
  text_element["content"] = text_element["content"].decode("utf-8")
148
152
  return text_element
@@ -150,39 +154,6 @@ class Text(LocalElement, TextBase):
150
154
 
151
155
  @dataclass
152
156
  class Pdf(Element):
153
- """Useful to send a pdf (remote or local) to the UI."""
157
+ """Useful to send a pdf to the UI."""
154
158
 
155
159
  type: ElementType = "pdf"
156
- url: str = None
157
- path: str = None
158
- content: bytes = None
159
-
160
- def persist(self, client: BaseClient, for_id: str = None):
161
- if not self.content and not self.url:
162
- raise ValueError("Must provide content or url")
163
-
164
- # Either upload the content or use the url
165
- url = None
166
- if self.content:
167
- url = client.upload_element(content=self.content, mime="application/pdf")
168
- else:
169
- url = self.url
170
-
171
- if url:
172
- element = client.create_element(
173
- name=self.name,
174
- url=url,
175
- type=self.type,
176
- display=self.display,
177
- for_id=for_id,
178
- )
179
- return element
180
-
181
- def __post_init__(self):
182
- if self.path:
183
- with open(self.path, "rb") as f:
184
- self.content = f.read()
185
- elif self.content or self.url:
186
- pass # do nothing here
187
- else:
188
- raise ValueError("Must provide either path, content or url")