chainlit 1.1.0__py3-none-any.whl → 1.1.0rc0__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.

@@ -1,6 +0,0 @@
1
- try:
2
- import slack_bolt
3
- except ModuleNotFoundError:
4
- raise ValueError(
5
- "The slack_bolt package is required to integrate Chainlit with a Slack app. Run `pip install slack_bolt --upgrade`"
6
- )
chainlit/slack/app.py DELETED
@@ -1,368 +0,0 @@
1
- import asyncio
2
- import os
3
- import re
4
- import uuid
5
- from functools import partial
6
- from typing import Dict, List, Optional, Union
7
-
8
- import httpx
9
- from chainlit.config import config
10
- from chainlit.context import ChainlitContext, HTTPSession, context_var
11
- from chainlit.data import get_data_layer
12
- from chainlit.element import Element, ElementDict
13
- from chainlit.emitter import BaseChainlitEmitter
14
- from chainlit.message import Message, StepDict
15
- from chainlit.types import Feedback
16
- from chainlit.user import PersistedUser, User
17
- from chainlit.user_session import user_session
18
- from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
19
- from slack_bolt.async_app import AsyncApp
20
-
21
-
22
- class SlackEmitter(BaseChainlitEmitter):
23
- def __init__(
24
- self,
25
- session: HTTPSession,
26
- app: AsyncApp,
27
- channel_id: str,
28
- say,
29
- enabled=False,
30
- thread_ts: Optional[str] = None,
31
- ):
32
- super().__init__(session)
33
- self.app = app
34
- self.channel_id = channel_id
35
- self.say = say
36
- self.enabled = enabled
37
- self.thread_ts = thread_ts
38
-
39
- async def send_element(self, element_dict: ElementDict):
40
- if not self.enabled or element_dict.get("display") != "inline":
41
- return
42
-
43
- persisted_file = self.session.files.get(element_dict.get("chainlitKey") or "")
44
- file: Optional[Union[bytes, str]] = None
45
-
46
- if persisted_file:
47
- file = str(persisted_file["path"])
48
- elif file_url := element_dict.get("url"):
49
- async with httpx.AsyncClient() as client:
50
- response = await client.get(file_url)
51
- if response.status_code == 200:
52
- file = response.content
53
-
54
- if not file:
55
- return
56
-
57
- await self.app.client.files_upload_v2(
58
- channel=self.channel_id,
59
- thread_ts=self.thread_ts,
60
- file=file,
61
- title=element_dict.get("name"),
62
- )
63
-
64
- async def send_step(self, step_dict: StepDict):
65
- if not self.enabled:
66
- return
67
-
68
- is_chain_of_thought = bool(step_dict.get("parentId"))
69
- is_empty_output = not step_dict.get("output")
70
-
71
- if is_chain_of_thought or is_empty_output:
72
- return
73
-
74
- enable_feedback = not step_dict.get("disableFeedback") and get_data_layer()
75
- blocks: List[Dict] = [
76
- {
77
- "type": "section",
78
- "text": {"type": "mrkdwn", "text": step_dict["output"]},
79
- }
80
- ]
81
- if enable_feedback:
82
- blocks.append(
83
- {
84
- "type": "actions",
85
- "elements": [
86
- {
87
- "action_id": "thumbdown",
88
- "type": "button",
89
- "text": {
90
- "type": "plain_text",
91
- "emoji": True,
92
- "text": ":thumbsdown:",
93
- },
94
- "value": step_dict.get("id"),
95
- },
96
- {
97
- "action_id": "thumbup",
98
- "type": "button",
99
- "text": {
100
- "type": "plain_text",
101
- "emoji": True,
102
- "text": ":thumbsup:",
103
- },
104
- "value": step_dict.get("id"),
105
- },
106
- ],
107
- }
108
- )
109
- await self.say(
110
- text=step_dict["output"], blocks=blocks, thread_ts=self.thread_ts
111
- )
112
-
113
- async def update_step(self, step_dict: StepDict):
114
- if not self.enabled:
115
- return
116
-
117
- await self.send_step(step_dict)
118
-
119
-
120
- slack_app = AsyncApp(
121
- token=os.environ.get("SLACK_BOT_TOKEN"),
122
- signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
123
- )
124
-
125
-
126
- def init_slack_context(
127
- session: HTTPSession,
128
- slack_channel_id: str,
129
- event,
130
- say,
131
- thread_ts: Optional[str] = None,
132
- ) -> ChainlitContext:
133
- emitter = SlackEmitter(
134
- session=session,
135
- app=slack_app,
136
- channel_id=slack_channel_id,
137
- say=say,
138
- thread_ts=thread_ts,
139
- )
140
- context = ChainlitContext(session=session, emitter=emitter)
141
- context_var.set(context)
142
- user_session.set("slack_event", event)
143
- user_session.set(
144
- "fetch_slack_message_history",
145
- partial(
146
- fetch_message_history, channel_id=slack_channel_id, thread_ts=thread_ts
147
- ),
148
- )
149
- return context
150
-
151
-
152
- slack_app_handler = AsyncSlackRequestHandler(slack_app)
153
-
154
- users_by_slack_id: Dict[str, Union[User, PersistedUser]] = {}
155
-
156
- USER_PREFIX = "slack_"
157
-
158
-
159
- def clean_content(message: str):
160
- cleaned_text = re.sub(r"<@[\w]+>", "", message).strip()
161
- return cleaned_text
162
-
163
-
164
- async def get_user(slack_user_id: str):
165
- slack_user = await slack_app.client.users_info(user=slack_user_id)
166
- slack_user_profile = slack_user["user"]["profile"]
167
-
168
- user_email = slack_user_profile.get("email")
169
- user = User(identifier=USER_PREFIX + user_email, metadata=slack_user_profile)
170
-
171
- users_by_slack_id[slack_user_id] = user
172
-
173
- if data_layer := get_data_layer():
174
- persisted_user = await data_layer.create_user(user)
175
- if persisted_user:
176
- users_by_slack_id[slack_user_id] = persisted_user
177
-
178
- return users_by_slack_id[slack_user_id]
179
-
180
-
181
- async def fetch_message_history(
182
- channel_id: str, thread_ts: Optional[str] = None, limit=30
183
- ):
184
- if not thread_ts:
185
- result = await slack_app.client.conversations_history(
186
- channel=channel_id, limit=limit
187
- )
188
- else:
189
- result = await slack_app.client.conversations_replies(
190
- channel=channel_id, ts=thread_ts, limit=limit
191
- )
192
- if result["ok"]:
193
- messages = result["messages"]
194
- return messages
195
- else:
196
- raise Exception(f"Failed to fetch messages: {result['error']}")
197
-
198
-
199
- async def download_slack_file(url, token):
200
- headers = {"Authorization": f"Bearer {token}"}
201
- async with httpx.AsyncClient() as client:
202
- response = await client.get(url, headers=headers)
203
- if response.status_code == 200:
204
- return response.content
205
- else:
206
- return None
207
-
208
-
209
- async def download_slack_files(session: HTTPSession, files, token):
210
- download_coros = [
211
- download_slack_file(file.get("url_private"), token) for file in files
212
- ]
213
- file_bytes_list = await asyncio.gather(*download_coros)
214
- file_refs = []
215
- for idx, file_bytes in enumerate(file_bytes_list):
216
- if file_bytes:
217
- name = files[idx].get("name")
218
- mime_type = files[idx].get("mimetype")
219
- file_ref = await session.persist_file(
220
- name=name, mime=mime_type, content=file_bytes
221
- )
222
- file_refs.append(file_ref)
223
-
224
- files_dicts = [
225
- session.files[file["id"]] for file in file_refs if file["id"] in session.files
226
- ]
227
-
228
- file_elements = [Element.from_dict(file_dict) for file_dict in files_dicts]
229
-
230
- return file_elements
231
-
232
-
233
- async def process_slack_message(
234
- event,
235
- say,
236
- thread_name: Optional[str] = None,
237
- bind_thread_to_user=False,
238
- thread_ts: Optional[str] = None,
239
- ):
240
- user = await get_user(event["user"])
241
-
242
- channel_id = event["channel"]
243
- thread_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, thread_ts or channel_id))
244
-
245
- text = event.get("text")
246
- slack_files = event.get("files", [])
247
-
248
- session_id = str(uuid.uuid4())
249
- session = HTTPSession(
250
- id=session_id,
251
- thread_id=thread_id,
252
- user=user,
253
- client_type="slack",
254
- )
255
-
256
- ctx = init_slack_context(
257
- session=session,
258
- slack_channel_id=channel_id,
259
- event=event,
260
- say=say,
261
- thread_ts=thread_ts,
262
- )
263
-
264
- file_elements = await download_slack_files(
265
- session, slack_files, slack_app.client.token
266
- )
267
-
268
- msg = Message(
269
- content=clean_content(text),
270
- elements=file_elements,
271
- type="user_message",
272
- author=user.metadata.get("real_name"),
273
- )
274
-
275
- await msg.send()
276
-
277
- ctx.emitter.enabled = True
278
-
279
- if on_chat_start := config.code.on_chat_start:
280
- await on_chat_start()
281
-
282
- if on_message := config.code.on_message:
283
- await on_message(msg)
284
-
285
- if on_chat_end := config.code.on_chat_end:
286
- await on_chat_end()
287
-
288
- if data_layer := get_data_layer():
289
- user_id = None
290
- if isinstance(user, PersistedUser):
291
- user_id = user.id if bind_thread_to_user else None
292
-
293
- await data_layer.update_thread(
294
- thread_id=thread_id,
295
- name=thread_name or msg.content,
296
- metadata=ctx.session.to_persistable(),
297
- user_id=user_id,
298
- )
299
-
300
- ctx.session.delete()
301
-
302
-
303
- @slack_app.event("app_home_opened")
304
- async def handle_app_home_opened(event, say):
305
- pass
306
-
307
-
308
- @slack_app.event("app_mention")
309
- async def handle_app_mentions(event, say):
310
- thread_ts = event.get("thread_ts", event["ts"])
311
- await process_slack_message(event, say, thread_ts=thread_ts)
312
-
313
-
314
- @slack_app.event("message")
315
- async def handle_message(message, say):
316
- user = await get_user(message["user"])
317
- thread_name = f"{user.identifier} Slack DM"
318
- await process_slack_message(message, say, thread_name)
319
-
320
-
321
- @slack_app.block_action("thumbdown")
322
- async def thumb_down(ack, context, body):
323
- await ack()
324
- step_id = body["actions"][0]["value"]
325
-
326
- if data_layer := get_data_layer():
327
- await data_layer.upsert_feedback(Feedback(forId=step_id, value=0))
328
-
329
- text = body["message"]["text"]
330
- blocks = body["message"]["blocks"]
331
- updated_blocks = [block for block in blocks if block["type"] != "actions"]
332
- updated_blocks.append(
333
- {
334
- "type": "section",
335
- "text": {"type": "mrkdwn", "text": ":thumbsdown: Feedback received."},
336
- }
337
- )
338
- await context.client.chat_update(
339
- channel=body["channel"]["id"],
340
- ts=body["container"]["message_ts"],
341
- text=text,
342
- blocks=updated_blocks,
343
- )
344
-
345
-
346
- @slack_app.block_action("thumbup")
347
- async def thumb_up(ack, context, body):
348
- await ack()
349
- step_id = body["actions"][0]["value"]
350
-
351
- if data_layer := get_data_layer():
352
- await data_layer.upsert_feedback(Feedback(forId=step_id, value=1))
353
-
354
- text = body["message"]["text"]
355
- blocks = body["message"]["blocks"]
356
- updated_blocks = [block for block in blocks if block["type"] != "actions"]
357
- updated_blocks.append(
358
- {
359
- "type": "section",
360
- "text": {"type": "mrkdwn", "text": ":thumbsup: Feedback received."},
361
- }
362
- )
363
- await context.client.chat_update(
364
- channel=body["channel"]["id"],
365
- ts=body["container"]["message_ts"],
366
- text=text,
367
- blocks=updated_blocks,
368
- )