chainlit 1.1.0__py3-none-any.whl → 1.1.0rc1__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/discord/app.py DELETED
@@ -1,304 +0,0 @@
1
- import asyncio
2
- import mimetypes
3
- import re
4
- import uuid
5
- from io import BytesIO
6
- from typing import Dict, List, Optional, Union, TYPE_CHECKING
7
-
8
- if TYPE_CHECKING:
9
- from discord.abc import MessageableChannel
10
-
11
- import discord
12
-
13
- import filetype
14
- import httpx
15
- from chainlit.config import config
16
- from chainlit.context import ChainlitContext, HTTPSession, context_var
17
- from chainlit.data import get_data_layer
18
- from chainlit.element import Element, ElementDict
19
- from chainlit.emitter import BaseChainlitEmitter
20
- from chainlit.logger import logger
21
- from chainlit.message import Message, StepDict
22
- from chainlit.types import Feedback
23
- from chainlit.user import PersistedUser, User
24
- from chainlit.user_session import user_session
25
- from discord.ui import Button, View
26
-
27
-
28
- class FeedbackView(View):
29
- def __init__(self, step_id: str):
30
- super().__init__(timeout=None)
31
- self.step_id = step_id
32
-
33
- @discord.ui.button(label="👎")
34
- async def thumbs_down(self, interaction: discord.Interaction, button: Button):
35
- if data_layer := get_data_layer():
36
- await data_layer.upsert_feedback(Feedback(forId=self.step_id, value=0))
37
- if interaction.message:
38
- await interaction.message.edit(view=None)
39
- await interaction.message.add_reaction("👎")
40
-
41
- @discord.ui.button(label="👍")
42
- async def thumbs_up(self, interaction: discord.Interaction, button: Button):
43
- if data_layer := get_data_layer():
44
- await data_layer.upsert_feedback(Feedback(forId=self.step_id, value=1))
45
- if interaction.message:
46
- await interaction.message.edit(view=None)
47
- await interaction.message.add_reaction("👍")
48
-
49
-
50
- class DiscordEmitter(BaseChainlitEmitter):
51
- def __init__(
52
- self, session: HTTPSession, channel: "MessageableChannel", enabled=False
53
- ):
54
- super().__init__(session)
55
- self.channel = channel
56
- self.enabled = enabled
57
-
58
- async def send_element(self, element_dict: ElementDict):
59
- if not self.enabled or element_dict.get("display") != "inline":
60
- return
61
-
62
- persisted_file = self.session.files.get(element_dict.get("chainlitKey") or "")
63
- file: Optional[Union[BytesIO, str]] = None
64
- mime: Optional[str] = None
65
-
66
- if persisted_file:
67
- file = str(persisted_file["path"])
68
- mime = element_dict.get("mime")
69
- elif file_url := element_dict.get("url"):
70
- async with httpx.AsyncClient() as client:
71
- response = await client.get(file_url)
72
- if response.status_code == 200:
73
- file = BytesIO(response.content)
74
- mime = filetype.guess_mime(file)
75
-
76
- if not file:
77
- return
78
-
79
- element_name: str = element_dict.get("name", "Untitled")
80
-
81
- if mime:
82
- file_extension = mimetypes.guess_extension(mime)
83
- if file_extension:
84
- element_name += file_extension
85
-
86
- file_obj = discord.File(file, filename=element_name)
87
- await self.channel.send(file=file_obj)
88
-
89
- async def send_step(self, step_dict: StepDict):
90
- if not self.enabled:
91
- return
92
-
93
- is_chain_of_thought = bool(step_dict.get("parentId"))
94
- is_empty_output = not step_dict.get("output")
95
-
96
- if is_chain_of_thought or is_empty_output:
97
- return
98
- else:
99
- enable_feedback = not step_dict.get("disableFeedback") and get_data_layer()
100
- message = await self.channel.send(step_dict["output"])
101
-
102
- if enable_feedback:
103
- view = FeedbackView(step_dict.get("id", ""))
104
- await message.edit(view=view)
105
-
106
- async def update_step(self, step_dict: StepDict):
107
- if not self.enabled:
108
- return
109
-
110
- await self.send_step(step_dict)
111
-
112
-
113
- intents = discord.Intents.default()
114
- intents.message_content = True
115
-
116
- client = discord.Client(intents=intents)
117
-
118
-
119
- def init_discord_context(
120
- session: HTTPSession,
121
- channel: "MessageableChannel",
122
- message: discord.Message,
123
- ) -> ChainlitContext:
124
- emitter = DiscordEmitter(session=session, channel=channel)
125
- context = ChainlitContext(session=session, emitter=emitter)
126
- context_var.set(context)
127
- user_session.set("discord_message", message)
128
- user_session.set("discord_channel", channel)
129
- return context
130
-
131
-
132
- users_by_discord_id: Dict[int, Union[User, PersistedUser]] = {}
133
-
134
- USER_PREFIX = "discord_"
135
-
136
-
137
- async def get_user(discord_user: Union[discord.User, discord.Member]):
138
- metadata = {
139
- "name": discord_user.name,
140
- "id": discord_user.id,
141
- }
142
- user = User(identifier=USER_PREFIX + str(discord_user.name), metadata=metadata)
143
-
144
- users_by_discord_id[discord_user.id] = user
145
-
146
- if data_layer := get_data_layer():
147
- persisted_user = await data_layer.create_user(user)
148
- if persisted_user:
149
- users_by_discord_id[discord_user.id] = persisted_user
150
-
151
- return users_by_discord_id[discord_user.id]
152
-
153
-
154
- async def download_discord_file(url: str):
155
- async with httpx.AsyncClient() as client:
156
- response = await client.get(url)
157
- if response.status_code == 200:
158
- return response.content
159
- else:
160
- return None
161
-
162
-
163
- async def download_discord_files(
164
- session: HTTPSession, attachments: List[discord.Attachment]
165
- ):
166
- download_coros = [
167
- download_discord_file(attachment.url) for attachment in attachments
168
- ]
169
- file_bytes_list = await asyncio.gather(*download_coros)
170
- file_refs = []
171
- for idx, file_bytes in enumerate(file_bytes_list):
172
- if file_bytes:
173
- name = attachments[idx].filename
174
- mime_type = attachments[idx].content_type or "application/octet-stream"
175
- file_ref = await session.persist_file(
176
- name=name, mime=mime_type, content=file_bytes
177
- )
178
- file_refs.append(file_ref)
179
-
180
- files_dicts = [
181
- session.files[file["id"]] for file in file_refs if file["id"] in session.files
182
- ]
183
-
184
- file_elements = [Element.from_dict(file_dict) for file_dict in files_dicts]
185
-
186
- return file_elements
187
-
188
-
189
- def clean_content(message: discord.Message):
190
- if not client.user:
191
- return message.content
192
-
193
- # Regex to find mentions of the bot
194
- bot_mention = f"<@!?{client.user.id}>"
195
- # Replace the bot's mention with nothing
196
- return re.sub(bot_mention, "", message.content).strip()
197
-
198
-
199
- async def process_discord_message(
200
- message: discord.Message,
201
- thread_name: str,
202
- channel: "MessageableChannel",
203
- bind_thread_to_user=False,
204
- ):
205
- user = await get_user(message.author)
206
-
207
- thread_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, str(channel.id)))
208
-
209
- text = clean_content(message)
210
- discord_files = message.attachments
211
-
212
- session_id = str(uuid.uuid4())
213
- session = HTTPSession(
214
- id=session_id,
215
- thread_id=thread_id,
216
- user=user,
217
- client_type="discord",
218
- )
219
-
220
- ctx = init_discord_context(
221
- session=session,
222
- channel=channel,
223
- message=message,
224
- )
225
-
226
- file_elements = await download_discord_files(session, discord_files)
227
-
228
- msg = Message(
229
- content=text,
230
- elements=file_elements,
231
- type="user_message",
232
- author=user.metadata.get("name"),
233
- )
234
-
235
- await msg.send()
236
-
237
- ctx.emitter.enabled = True
238
-
239
- if on_chat_start := config.code.on_chat_start:
240
- await on_chat_start()
241
-
242
- if on_message := config.code.on_message:
243
- await on_message(msg)
244
-
245
- if on_chat_end := config.code.on_chat_end:
246
- await on_chat_end()
247
-
248
- if data_layer := get_data_layer():
249
- user_id = None
250
- if isinstance(user, PersistedUser):
251
- user_id = user.id if bind_thread_to_user else None
252
-
253
- await data_layer.update_thread(
254
- thread_id=thread_id,
255
- name=thread_name,
256
- metadata=ctx.session.to_persistable(),
257
- user_id=user_id,
258
- )
259
-
260
- ctx.session.delete()
261
-
262
-
263
- @client.event
264
- async def on_ready():
265
- logger.info(f"Logged in as {client.user}")
266
-
267
-
268
- @client.event
269
- async def on_message(message: discord.Message):
270
- if not client.user or message.author == client.user:
271
- return
272
-
273
- is_dm = isinstance(message.channel, discord.DMChannel)
274
- if not client.user.mentioned_in(message) and not is_dm:
275
- return
276
-
277
- thread_name: str = ""
278
- bind_thread_to_user = False
279
- channel = message.channel
280
-
281
- if isinstance(message.channel, discord.Thread):
282
- thread_name = f"{message.channel.name}"
283
- elif isinstance(message.channel, discord.ForumChannel):
284
- thread_name = f"{message.channel.name}"
285
- elif isinstance(message.channel, discord.DMChannel):
286
- thread_name = f"{message.author} Discord DM"
287
- bind_thread_to_user = True
288
- elif isinstance(message.channel, discord.GroupChannel):
289
- thread_name = f"{message.channel.name}"
290
- elif isinstance(message.channel, discord.TextChannel):
291
- channel = await message.channel.create_thread(
292
- name=clean_content(message), message=message
293
- )
294
- thread_name = f"{channel.name}"
295
- else:
296
- logger.warning(f"Unsupported channel type: {message.channel.type}")
297
- return
298
-
299
- await process_discord_message(
300
- message=message,
301
- thread_name=thread_name,
302
- channel=channel,
303
- bind_thread_to_user=bind_thread_to_user,
304
- )