chainlit 1.1.300rc4__py3-none-any.whl → 1.1.300rc5__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 (43) hide show
  1. chainlit/__init__.py +20 -1
  2. chainlit/cli/__init__.py +48 -6
  3. chainlit/config.py +5 -0
  4. chainlit/context.py +9 -0
  5. chainlit/copilot/dist/index.js +180 -180
  6. chainlit/data/__init__.py +6 -3
  7. chainlit/data/sql_alchemy.py +3 -3
  8. chainlit/element.py +33 -9
  9. chainlit/emitter.py +3 -3
  10. chainlit/frontend/dist/assets/{DailyMotion-1a2b7d60.js → DailyMotion-f9db5a1d.js} +1 -1
  11. chainlit/frontend/dist/assets/{Facebook-8422f48c.js → Facebook-f95b29c9.js} +1 -1
  12. chainlit/frontend/dist/assets/{FilePlayer-a0a41349.js → FilePlayer-ba3f562c.js} +1 -1
  13. chainlit/frontend/dist/assets/{Kaltura-aa7990f2.js → Kaltura-195ed801.js} +1 -1
  14. chainlit/frontend/dist/assets/{Mixcloud-1647b3e4.js → Mixcloud-f64c6d87.js} +1 -1
  15. chainlit/frontend/dist/assets/{Mux-7e57be81.js → Mux-206cbddc.js} +1 -1
  16. chainlit/frontend/dist/assets/{Preview-cb89b2c6.js → Preview-af249586.js} +1 -1
  17. chainlit/frontend/dist/assets/{SoundCloud-c0d86d55.js → SoundCloud-80a26cdf.js} +1 -1
  18. chainlit/frontend/dist/assets/{Streamable-57c43c18.js → Streamable-f80b255d.js} +1 -1
  19. chainlit/frontend/dist/assets/{Twitch-bed3f21d.js → Twitch-0e2f1d13.js} +1 -1
  20. chainlit/frontend/dist/assets/{Vidyard-4dd76e44.js → Vidyard-bd67bfc6.js} +1 -1
  21. chainlit/frontend/dist/assets/{Vimeo-93bb5ae2.js → Vimeo-f9496a5d.js} +1 -1
  22. chainlit/frontend/dist/assets/{Wistia-97199246.js → Wistia-a943e0aa.js} +1 -1
  23. chainlit/frontend/dist/assets/{YouTube-fe1a7afe.js → YouTube-cf572a1f.js} +1 -1
  24. chainlit/frontend/dist/assets/{index-919bea8f.js → index-5511e258.js} +131 -131
  25. chainlit/frontend/dist/assets/{react-plotly-b416b8f9.js → react-plotly-74b55763.js} +1 -1
  26. chainlit/frontend/dist/index.html +1 -2
  27. chainlit/message.py +13 -8
  28. chainlit/oauth_providers.py +63 -1
  29. chainlit/server.py +170 -48
  30. chainlit/socket.py +40 -20
  31. chainlit/step.py +12 -3
  32. chainlit/teams/__init__.py +6 -0
  33. chainlit/teams/app.py +332 -0
  34. chainlit/translations/en-US.json +1 -1
  35. chainlit/types.py +7 -17
  36. chainlit/user.py +9 -1
  37. chainlit/utils.py +42 -0
  38. {chainlit-1.1.300rc4.dist-info → chainlit-1.1.300rc5.dist-info}/METADATA +2 -2
  39. chainlit-1.1.300rc5.dist-info/RECORD +79 -0
  40. chainlit/cli/utils.py +0 -24
  41. chainlit-1.1.300rc4.dist-info/RECORD +0 -78
  42. {chainlit-1.1.300rc4.dist-info → chainlit-1.1.300rc5.dist-info}/WHEEL +0 -0
  43. {chainlit-1.1.300rc4.dist-info → chainlit-1.1.300rc5.dist-info}/entry_points.txt +0 -0
chainlit/socket.py CHANGED
@@ -2,8 +2,8 @@ import asyncio
2
2
  import json
3
3
  import time
4
4
  import uuid
5
- from urllib.parse import unquote
6
5
  from typing import Any, Dict, Literal
6
+ from urllib.parse import unquote
7
7
 
8
8
  from chainlit.action import Action
9
9
  from chainlit.auth import get_current_user, require_login
@@ -13,14 +13,15 @@ from chainlit.data import get_data_layer
13
13
  from chainlit.element import Element
14
14
  from chainlit.logger import logger
15
15
  from chainlit.message import ErrorMessage, Message
16
- from chainlit.server import socket
16
+ from chainlit.server import sio
17
17
  from chainlit.session import WebsocketSession
18
18
  from chainlit.telemetry import trace_event
19
19
  from chainlit.types import (
20
20
  AudioChunk,
21
21
  AudioChunkPayload,
22
22
  AudioEndPayload,
23
- UIMessagePayload,
23
+ SystemMessagePayload,
24
+ UserMessagePayload,
24
25
  )
25
26
  from chainlit.user_session import user_sessions
26
27
 
@@ -53,7 +54,7 @@ async def resume_thread(session: WebsocketSession):
53
54
  user_is_author = author == session.user.identifier
54
55
 
55
56
  if user_is_author:
56
- metadata = thread.get("metadata", {})
57
+ metadata = thread.get("metadata") or {}
57
58
  user_sessions[session.id] = metadata.copy()
58
59
  if chat_profile := metadata.get("chat_profile"):
59
60
  session.chat_profile = chat_profile
@@ -98,8 +99,8 @@ def build_anon_user_identifier(environ):
98
99
  return str(uuid.uuid5(uuid.NAMESPACE_DNS, ip))
99
100
 
100
101
 
101
- @socket.on("connect")
102
- async def connect(sid, environ, auth):
102
+ @sio.on("connect")
103
+ async def connect(sid, environ):
103
104
  if (
104
105
  not config.code.on_chat_start
105
106
  and not config.code.on_message
@@ -124,11 +125,11 @@ async def connect(sid, environ, auth):
124
125
 
125
126
  # Session scoped function to emit to the client
126
127
  def emit_fn(event, data):
127
- return socket.emit(event, data, to=sid)
128
+ return sio.emit(event, data, to=sid)
128
129
 
129
130
  # Session scoped function to emit to the client and wait for a response
130
131
  def emit_call_fn(event: Literal["ask", "call_fn"], data, timeout):
131
- return socket.call(event, data, timeout=timeout, to=sid)
132
+ return sio.call(event, data, timeout=timeout, to=sid)
132
133
 
133
134
  session_id = environ.get("HTTP_X_CHAINLIT_SESSION_ID")
134
135
  if restore_existing_session(sid, session_id, emit_fn, emit_call_fn):
@@ -140,7 +141,9 @@ async def connect(sid, environ, auth):
140
141
  client_type = environ.get("HTTP_X_CHAINLIT_CLIENT_TYPE")
141
142
  http_referer = environ.get("HTTP_REFERER")
142
143
  url_encoded_chat_profile = environ.get("HTTP_X_CHAINLIT_CHAT_PROFILE")
143
- chat_profile = unquote(url_encoded_chat_profile) if url_encoded_chat_profile else None
144
+ chat_profile = (
145
+ unquote(url_encoded_chat_profile) if url_encoded_chat_profile else None
146
+ )
144
147
 
145
148
  ws_session = WebsocketSession(
146
149
  id=session_id,
@@ -161,7 +164,7 @@ async def connect(sid, environ, auth):
161
164
  return True
162
165
 
163
166
 
164
- @socket.on("connection_successful")
167
+ @sio.on("connection_successful")
165
168
  async def connection_successful(sid):
166
169
  context = init_ws_context(sid)
167
170
 
@@ -189,14 +192,14 @@ async def connection_successful(sid):
189
192
  context.session.current_task = task
190
193
 
191
194
 
192
- @socket.on("clear_session")
195
+ @sio.on("clear_session")
193
196
  async def clean_session(sid):
194
197
  session = WebsocketSession.get(sid)
195
198
  if session:
196
199
  session.to_clear = True
197
200
 
198
201
 
199
- @socket.on("disconnect")
202
+ @sio.on("disconnect")
200
203
  async def disconnect(sid):
201
204
  session = WebsocketSession.get(sid)
202
205
 
@@ -230,7 +233,7 @@ async def disconnect(sid):
230
233
  asyncio.ensure_future(clear_on_timeout(sid))
231
234
 
232
235
 
233
- @socket.on("stop")
236
+ @sio.on("stop")
234
237
  async def stop(sid):
235
238
  if session := WebsocketSession.get(sid):
236
239
  trace_event("stop_task")
@@ -245,7 +248,7 @@ async def stop(sid):
245
248
  await config.code.on_stop()
246
249
 
247
250
 
248
- async def process_message(session: WebsocketSession, payload: UIMessagePayload):
251
+ async def process_message(session: WebsocketSession, payload: UserMessagePayload):
249
252
  """Process a message from the user."""
250
253
  try:
251
254
  context = init_ws_context(session)
@@ -267,8 +270,8 @@ async def process_message(session: WebsocketSession, payload: UIMessagePayload):
267
270
  await context.emitter.task_end()
268
271
 
269
272
 
270
- @socket.on("ui_message")
271
- async def message(sid, payload: UIMessagePayload):
273
+ @sio.on("user_message")
274
+ async def message(sid, payload: UserMessagePayload):
272
275
  """Handle a message sent by the User."""
273
276
  session = WebsocketSession.require(sid)
274
277
 
@@ -276,7 +279,24 @@ async def message(sid, payload: UIMessagePayload):
276
279
  session.current_task = task
277
280
 
278
281
 
279
- @socket.on("audio_chunk")
282
+ @sio.on("system_message")
283
+ async def on_system_message(sid, payload: SystemMessagePayload):
284
+ """Handle a message sent by the User."""
285
+ session = WebsocketSession.require(sid)
286
+
287
+ init_ws_context(session)
288
+ message = Message(
289
+ type="system_message",
290
+ content=payload["content"],
291
+ metadata=payload.get("metadata"),
292
+ )
293
+ asyncio.create_task(message._create())
294
+
295
+ if config.code.on_system_message:
296
+ await config.code.on_system_message(message)
297
+
298
+
299
+ @sio.on("audio_chunk")
280
300
  async def audio_chunk(sid, payload: AudioChunkPayload):
281
301
  """Handle an audio chunk sent by the user."""
282
302
  session = WebsocketSession.require(sid)
@@ -287,7 +307,7 @@ async def audio_chunk(sid, payload: AudioChunkPayload):
287
307
  asyncio.create_task(config.code.on_audio_chunk(AudioChunk(**payload)))
288
308
 
289
309
 
290
- @socket.on("audio_end")
310
+ @sio.on("audio_end")
291
311
  async def audio_end(sid, payload: AudioEndPayload):
292
312
  """Handle the end of the audio stream."""
293
313
  session = WebsocketSession.require(sid)
@@ -331,7 +351,7 @@ async def process_action(action: Action):
331
351
  logger.warning("No callback found for action %s", action.name)
332
352
 
333
353
 
334
- @socket.on("action_call")
354
+ @sio.on("action_call")
335
355
  async def call_action(sid, action):
336
356
  """Handle an action call from the UI."""
337
357
  context = init_ws_context(sid)
@@ -358,7 +378,7 @@ async def call_action(sid, action):
358
378
  )
359
379
 
360
380
 
361
- @socket.on("chat_settings_change")
381
+ @sio.on("chat_settings_change")
362
382
  async def change_settings(sid, settings: Dict[str, Any]):
363
383
  """Handle change settings submit from the UI."""
364
384
  context = init_ws_context(sid)
chainlit/step.py CHANGED
@@ -295,7 +295,10 @@ class Step:
295
295
  tasks = [el.send(for_id=self.id) for el in self.elements]
296
296
  await asyncio.gather(*tasks)
297
297
 
298
- if config.ui.hide_cot and (self.parent_id or "message" not in self.type):
298
+ if config.ui.hide_cot and self.type not in [
299
+ "user_message",
300
+ "assistant_message",
301
+ ]:
299
302
  return True
300
303
 
301
304
  await context.emitter.update_step(step_dict)
@@ -349,7 +352,10 @@ class Step:
349
352
  tasks = [el.send(for_id=self.id) for el in self.elements]
350
353
  await asyncio.gather(*tasks)
351
354
 
352
- if config.ui.hide_cot and (self.parent_id or "message" not in self.type):
355
+ if config.ui.hide_cot and self.type not in [
356
+ "user_message",
357
+ "assistant_message",
358
+ ]:
353
359
  return self
354
360
 
355
361
  await context.emitter.send_step(step_dict)
@@ -374,7 +380,10 @@ class Step:
374
380
 
375
381
  assert self.id
376
382
 
377
- if config.ui.hide_cot and (self.parent_id or "message" not in self.type):
383
+ if config.ui.hide_cot and self.type not in [
384
+ "user_message",
385
+ "assistant_message",
386
+ ]:
378
387
  return
379
388
 
380
389
  if not self.streaming:
@@ -0,0 +1,6 @@
1
+ try:
2
+ import botbuilder
3
+ except ModuleNotFoundError:
4
+ raise ValueError(
5
+ "The botbuilder-core package is required to integrate Chainlit with a Slack app. Run `pip install botbuilder-core --upgrade`"
6
+ )
chainlit/teams/app.py ADDED
@@ -0,0 +1,332 @@
1
+ import asyncio
2
+ import base64
3
+ import mimetypes
4
+ import os
5
+ import uuid
6
+ from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Union
7
+
8
+ import filetype
9
+
10
+ if TYPE_CHECKING:
11
+ from botbuilder.core import TurnContext
12
+ from botbuilder.schema import Activity
13
+
14
+ import httpx
15
+ from botbuilder.core import (
16
+ BotFrameworkAdapter,
17
+ BotFrameworkAdapterSettings,
18
+ MessageFactory,
19
+ TurnContext,
20
+ )
21
+ from botbuilder.schema import (
22
+ ActionTypes,
23
+ Activity,
24
+ ActivityTypes,
25
+ Attachment,
26
+ CardAction,
27
+ ChannelAccount,
28
+ HeroCard,
29
+ )
30
+ from chainlit.config import config
31
+ from chainlit.context import ChainlitContext, HTTPSession, context_var
32
+ from chainlit.data import get_data_layer
33
+ from chainlit.element import Element, ElementDict
34
+ from chainlit.emitter import BaseChainlitEmitter
35
+ from chainlit.logger import logger
36
+ from chainlit.message import Message, StepDict
37
+ from chainlit.telemetry import trace
38
+ from chainlit.types import Feedback
39
+ from chainlit.user import PersistedUser, User
40
+ from chainlit.user_session import user_session
41
+
42
+
43
+ class TeamsEmitter(BaseChainlitEmitter):
44
+ def __init__(self, session: HTTPSession, turn_context: TurnContext, enabled=False):
45
+ super().__init__(session)
46
+ self.turn_context = turn_context
47
+ self.enabled = enabled
48
+
49
+ async def send_element(self, element_dict: ElementDict):
50
+ if not self.enabled or element_dict.get("display") != "inline":
51
+ return
52
+
53
+ persisted_file = self.session.files.get(element_dict.get("chainlitKey") or "")
54
+ attachment: Optional[Attachment] = None
55
+ mime: Optional[str] = None
56
+
57
+ element_name: str = element_dict.get("name", "Untitled")
58
+
59
+ if mime:
60
+ file_extension = mimetypes.guess_extension(mime)
61
+ if file_extension:
62
+ element_name += file_extension
63
+
64
+ if persisted_file:
65
+ mime = element_dict.get("mime")
66
+ with open(persisted_file["path"], "rb") as file:
67
+ dencoded_string = base64.b64encode(file.read()).decode()
68
+ content_url = f"data:{mime};base64,{dencoded_string}"
69
+ attachment = Attachment(
70
+ content_type=mime, content_url=content_url, name=element_name
71
+ )
72
+
73
+ elif url := element_dict.get("url"):
74
+ attachment = Attachment(
75
+ content_type=mime, content_url=url, name=element_name
76
+ )
77
+
78
+ if not attachment:
79
+ return
80
+
81
+ await self.turn_context.send_activity(Activity(attachments=[attachment]))
82
+
83
+ async def send_step(self, step_dict: StepDict):
84
+ if not self.enabled:
85
+ return
86
+
87
+ step_type = step_dict.get("type")
88
+ is_message = step_type in [
89
+ "user_message",
90
+ "assistant_message",
91
+ ]
92
+ is_chain_of_thought = bool(step_dict.get("parentId"))
93
+ is_empty_output = not step_dict.get("output")
94
+
95
+ if is_chain_of_thought or is_empty_output or not is_message:
96
+ return
97
+ else:
98
+ reply = MessageFactory.text(step_dict["output"])
99
+ enable_feedback = not step_dict.get("disableFeedback") and get_data_layer()
100
+ if enable_feedback:
101
+ like_button = CardAction(
102
+ type=ActionTypes.message_back,
103
+ title="👍",
104
+ text="like",
105
+ value={"feedback": "like", "step_id": step_dict["id"]},
106
+ )
107
+ dislike_button = CardAction(
108
+ type=ActionTypes.message_back,
109
+ title="👎",
110
+ text="dislike",
111
+ value={"feedback": "dislike", "step_id": step_dict["id"]},
112
+ )
113
+ card = HeroCard(buttons=[like_button, dislike_button])
114
+ attachment = Attachment(
115
+ content_type="application/vnd.microsoft.card.hero", content=card
116
+ )
117
+ reply.attachments = [attachment]
118
+
119
+ await self.turn_context.send_activity(reply)
120
+
121
+ async def update_step(self, step_dict: StepDict):
122
+ if not self.enabled:
123
+ return
124
+
125
+ await self.send_step(step_dict)
126
+
127
+
128
+ adapter_settings = BotFrameworkAdapterSettings(
129
+ app_id=os.environ.get("TEAMS_APP_ID"),
130
+ app_password=os.environ.get("TEAMS_APP_PASSWORD"),
131
+ )
132
+ adapter = BotFrameworkAdapter(adapter_settings)
133
+
134
+
135
+ @trace
136
+ def init_teams_context(
137
+ session: HTTPSession,
138
+ turn_context: TurnContext,
139
+ ) -> ChainlitContext:
140
+ emitter = TeamsEmitter(session=session, turn_context=turn_context)
141
+ context = ChainlitContext(session=session, emitter=emitter)
142
+ context_var.set(context)
143
+ user_session.set("teams_turn_context", turn_context)
144
+ return context
145
+
146
+
147
+ users_by_teams_id: Dict[str, Union[User, PersistedUser]] = {}
148
+
149
+ USER_PREFIX = "teams_"
150
+
151
+
152
+ async def get_user(teams_user: ChannelAccount):
153
+ if teams_user.id in users_by_teams_id:
154
+ return users_by_teams_id[teams_user.id]
155
+
156
+ metadata = {
157
+ "name": teams_user.name,
158
+ "id": teams_user.id,
159
+ }
160
+ user = User(identifier=USER_PREFIX + str(teams_user.name), metadata=metadata)
161
+
162
+ users_by_teams_id[teams_user.id] = user
163
+
164
+ if data_layer := get_data_layer():
165
+ try:
166
+ persisted_user = await data_layer.create_user(user)
167
+ if persisted_user:
168
+ users_by_teams_id[teams_user.id] = persisted_user
169
+ except Exception as e:
170
+ logger.error(f"Error creating user: {e}")
171
+
172
+ return users_by_teams_id[teams_user.id]
173
+
174
+
175
+ async def download_teams_file(url: str):
176
+ async with httpx.AsyncClient() as client:
177
+ response = await client.get(url)
178
+ if response.status_code == 200:
179
+ return response.content
180
+ else:
181
+ return None
182
+
183
+
184
+ async def download_teams_files(
185
+ session: HTTPSession, attachments: Optional[List[Attachment]] = None
186
+ ):
187
+ if not attachments:
188
+ return []
189
+
190
+ attachments = [
191
+ attachment for attachment in attachments if isinstance(attachment.content, dict)
192
+ ]
193
+ download_coros = [
194
+ download_teams_file(attachment.content.get("downloadUrl"))
195
+ for attachment in attachments
196
+ ]
197
+ file_bytes_list = await asyncio.gather(*download_coros)
198
+ file_refs = []
199
+ for idx, file_bytes in enumerate(file_bytes_list):
200
+ if file_bytes:
201
+ name = attachments[idx].name
202
+ mime_type = filetype.guess_mime(file_bytes) or "application/octet-stream"
203
+ file_ref = await session.persist_file(
204
+ name=name, mime=mime_type, content=file_bytes
205
+ )
206
+ file_refs.append(file_ref)
207
+
208
+ files_dicts = [
209
+ session.files[file["id"]] for file in file_refs if file["id"] in session.files
210
+ ]
211
+
212
+ file_elements = [Element.from_dict(file_dict) for file_dict in files_dicts]
213
+
214
+ return file_elements
215
+
216
+
217
+ def clean_content(activity: Activity):
218
+ return activity.text.strip()
219
+
220
+
221
+ async def process_teams_message(
222
+ turn_context: TurnContext,
223
+ thread_name: str,
224
+ ):
225
+ user = await get_user(turn_context.activity.from_property)
226
+
227
+ thread_id = str(
228
+ uuid.uuid5(uuid.NAMESPACE_DNS, str(turn_context.activity.conversation.id))
229
+ )
230
+
231
+ text = clean_content(turn_context.activity)
232
+ teams_files = turn_context.activity.attachments
233
+
234
+ session_id = str(uuid.uuid4())
235
+
236
+ session = HTTPSession(
237
+ id=session_id,
238
+ thread_id=thread_id,
239
+ user=user,
240
+ client_type="teams",
241
+ )
242
+
243
+ ctx = init_teams_context(
244
+ session=session,
245
+ turn_context=turn_context,
246
+ )
247
+
248
+ file_elements = await download_teams_files(session, teams_files)
249
+
250
+ msg = Message(
251
+ content=text,
252
+ elements=file_elements,
253
+ type="user_message",
254
+ author=user.metadata.get("name"),
255
+ )
256
+
257
+ await msg.send()
258
+
259
+ ctx.emitter.enabled = True
260
+
261
+ if on_chat_start := config.code.on_chat_start:
262
+ await on_chat_start()
263
+
264
+ if on_message := config.code.on_message:
265
+ await on_message(msg)
266
+
267
+ if on_chat_end := config.code.on_chat_end:
268
+ await on_chat_end()
269
+
270
+ if data_layer := get_data_layer():
271
+ if isinstance(user, PersistedUser):
272
+ try:
273
+ await data_layer.update_thread(
274
+ thread_id=thread_id,
275
+ name=thread_name,
276
+ metadata=ctx.session.to_persistable(),
277
+ user_id=user.id,
278
+ )
279
+ except Exception as e:
280
+ logger.error(f"Error updating thread: {e}")
281
+
282
+ ctx.session.delete()
283
+
284
+
285
+ async def handle_message(turn_context: TurnContext):
286
+ if turn_context.activity.type == ActivityTypes.message:
287
+ if (
288
+ turn_context.activity.text == "like"
289
+ or turn_context.activity.text == "dislike"
290
+ ):
291
+ feedback_value: Literal[0, 1] = (
292
+ 0 if turn_context.activity.text == "dislike" else 1
293
+ )
294
+ step_id = turn_context.activity.value.get("step_id")
295
+ if data_layer := get_data_layer():
296
+ await data_layer.upsert_feedback(
297
+ Feedback(forId=step_id, value=feedback_value)
298
+ )
299
+ updated_text = "👍" if turn_context.activity.text == "like" else "👎"
300
+ # Update the existing message to remove the buttons
301
+ updated_message = Activity(
302
+ type=ActivityTypes.message,
303
+ id=turn_context.activity.reply_to_id,
304
+ text=updated_text,
305
+ attachments=[],
306
+ )
307
+ await turn_context.update_activity(updated_message)
308
+ else:
309
+ # Send typing activity
310
+ typing_activity = Activity(
311
+ type=ActivityTypes.typing,
312
+ from_property=turn_context.activity.recipient,
313
+ recipient=turn_context.activity.from_property,
314
+ conversation=turn_context.activity.conversation,
315
+ )
316
+ await turn_context.send_activity(typing_activity)
317
+ thread_name = f"{turn_context.activity.from_property.name} Teams DM"
318
+ await process_teams_message(turn_context, thread_name)
319
+
320
+
321
+ async def on_turn(turn_context: TurnContext):
322
+ await handle_message(turn_context)
323
+
324
+
325
+ # Create the main bot class
326
+ class TeamsBot:
327
+ async def on_turn(self, turn_context: TurnContext):
328
+ await on_turn(turn_context)
329
+
330
+
331
+ # Create the bot instance
332
+ bot = TeamsBot()
@@ -20,7 +20,7 @@
20
20
  "TaskList": {
21
21
  "title": "🗒️ Task List",
22
22
  "loading": "Loading...",
23
- "error": "An error occured"
23
+ "error": "An error occurred"
24
24
  }
25
25
  },
26
26
  "attachments": {
chainlit/types.py CHANGED
@@ -150,11 +150,16 @@ class FileDict(TypedDict):
150
150
  type: str
151
151
 
152
152
 
153
- class UIMessagePayload(TypedDict):
153
+ class UserMessagePayload(TypedDict):
154
154
  message: "StepDict"
155
155
  fileReferences: Optional[List[FileReference]]
156
156
 
157
157
 
158
+ class SystemMessagePayload(TypedDict):
159
+ content: str
160
+ metadata: Optional[Dict[str, Any]]
161
+
162
+
158
163
  class AudioChunkPayload(TypedDict):
159
164
  isStart: bool
160
165
  mimeType: str
@@ -193,21 +198,6 @@ class AskActionResponse(TypedDict):
193
198
  collapsed: bool
194
199
 
195
200
 
196
- class GenerationRequest(BaseModel):
197
- chatGeneration: Optional[ChatGeneration] = None
198
- completionGeneration: Optional[CompletionGeneration] = None
199
- userEnv: Dict[str, str]
200
-
201
- @property
202
- def generation(self):
203
- if self.chatGeneration:
204
- return self.chatGeneration
205
- return self.completionGeneration
206
-
207
- def is_chat(self):
208
- return self.chatGeneration is not None
209
-
210
-
211
201
  class DeleteThreadRequest(BaseModel):
212
202
  threadId: str
213
203
 
@@ -259,8 +249,8 @@ class FeedbackDict(TypedDict):
259
249
  @dataclass
260
250
  class Feedback:
261
251
  forId: str
262
- threadId: Optional[str]
263
252
  value: Literal[0, 1]
253
+ threadId: Optional[str] = None
264
254
  id: Optional[str] = None
265
255
  comment: Optional[str] = None
266
256
 
chainlit/user.py CHANGED
@@ -4,7 +4,15 @@ from dataclasses_json import DataClassJsonMixin
4
4
  from pydantic.dataclasses import Field, dataclass
5
5
 
6
6
  Provider = Literal[
7
- "credentials", "header", "github", "google", "azure-ad", "okta", "auth0", "descope"
7
+ "credentials",
8
+ "header",
9
+ "github",
10
+ "google",
11
+ "azure-ad",
12
+ "azure-ad-hybrid",
13
+ "okta",
14
+ "auth0",
15
+ "descope",
8
16
  ]
9
17
 
10
18
 
chainlit/utils.py CHANGED
@@ -1,12 +1,16 @@
1
1
  import functools
2
2
  import importlib
3
3
  import inspect
4
+ import os
4
5
  from asyncio import CancelledError
5
6
  from typing import Callable
6
7
 
8
+ import click
9
+ from chainlit.auth import ensure_jwt_secret
7
10
  from chainlit.context import context
8
11
  from chainlit.logger import logger
9
12
  from chainlit.message import ErrorMessage
13
+ from fastapi import FastAPI
10
14
  from packaging import version
11
15
 
12
16
 
@@ -87,3 +91,41 @@ def check_module_version(name, required_version):
87
91
  except ModuleNotFoundError:
88
92
  return False
89
93
  return version.parse(module.__version__) >= version.parse(required_version)
94
+
95
+
96
+ def check_file(target: str):
97
+ # Define accepted file extensions for Chainlit
98
+ ACCEPTED_FILE_EXTENSIONS = ("py", "py3")
99
+
100
+ _, extension = os.path.splitext(target)
101
+
102
+ # Check file extension
103
+ if extension[1:] not in ACCEPTED_FILE_EXTENSIONS:
104
+ if extension[1:] == "":
105
+ raise click.BadArgumentUsage(
106
+ "Chainlit requires raw Python (.py) files, but the provided file has no extension."
107
+ )
108
+ else:
109
+ raise click.BadArgumentUsage(
110
+ f"Chainlit requires raw Python (.py) files, not {extension}."
111
+ )
112
+
113
+ if not os.path.exists(target):
114
+ raise click.BadParameter(f"File does not exist: {target}")
115
+
116
+
117
+ def mount_chainlit(app: FastAPI, target: str, path="/chainlit"):
118
+ os.environ["CHAINLIT_ROOT_PATH"] = path
119
+ from chainlit.config import config, load_module
120
+ from chainlit.server import combined_asgi_app as chainlit_app
121
+
122
+ config.run.root_path = path
123
+
124
+ check_file(target)
125
+ # Load the module provided by the user
126
+ config.run.module_name = target
127
+ load_module(config.run.module_name)
128
+
129
+ ensure_jwt_secret()
130
+
131
+ app.mount("/", chainlit_app)